Cython permite escribir código parecido a Python que se traduce a C y se compila como una extensión. Su objetivo principal es mejorar el rendimiento de secciones críticas y facilitar la integración con bibliotecas en C. No convierte automáticamente cualquier programa en una aplicación ultrarrápida: los mejores resultados aparecen cuando se identifican cuellos de botella concretos y se añaden tipos estáticos a operaciones intensivas.
Cuándo considerar Cython
Cython es útil cuando un perfil muestra que gran parte del tiempo se consume en bucles numéricos, acceso repetido a atributos, llamadas de funciones pequeñas o interacción con APIs de C. Antes de compilar, mide el programa con la guía de cProfile en Python. Optimizar una parte que representa el 2% del tiempo total apenas cambiará la aplicación.
Qué genera Cython
Un archivo .pyx se transforma en código C. Después, un compilador crea una extensión que Python puede importar. El flujo general es:
archivo.pyx → código C → extensión compilada → import en PythonLa guía oficial de compilación de Cython explica los métodos principales para construir extensiones.
Instalar Cython
Crea un entorno virtual y ejecuta:
python -m pip install cython setuptoolsTambién necesitas un compilador de C compatible con tu plataforma. En Windows suele utilizarse Microsoft C++ Build Tools; en Linux, GCC; y en macOS, las herramientas de línea de comandos de Xcode.
Primer archivo .pyx
Crea calculos.pyx:
def sumar(int a, int b):
return a + bLos parámetros tienen tipos C. La función sigue siendo accesible desde Python.
Compilar con setup.py
Una configuración sencilla:
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize(
"calculos.pyx",
compiler_directives={"language_level": "3"},
)
)Compila en el mismo directorio:
python setup.py build_ext --inplaceDespués puedes importar:
from calculos import sumar
print(sumar(2, 3))Usar pyximport durante pruebas
Para experimentar:
import pyximport
pyximport.install(language_level=3)
import calculosEste método es cómodo en desarrollo, pero un proceso de construcción explícito resulta más reproducible para paquetes y producción.
Python puro frente a tipos C
Compilar código dinámico puede aportar una mejora moderada, pero los mayores beneficios aparecen al declarar variables:
def suma_cuadrados(int limite):
cdef long total = 0
cdef int i
for i in range(limite):
total += i * i
return totalcdef crea variables internas de C. Esto reduce creación de objetos Python y comprobaciones en cada iteración.
Funciones cdef, def y cpdef
def crea una función visible desde Python. cdef crea una función interna de C, más rápida de llamar desde código Cython, pero no importable directamente desde Python. cpdef genera una interfaz para ambos mundos.
cdef int interno(int x):
return x * 2
cpdef int duplicar(int x):
return interno(x)No conviertas todas las funciones a cdef. Mantén una interfaz Python clara y mueve solo las operaciones críticas al nivel compilado.
Type hints y modo Python puro
Cython también admite archivos .py con anotaciones y decoradores:
import cython
@cython.ccall
def multiplicar(a: cython.int, b: cython.int) -> cython.int:
return a * bEste enfoque mantiene un archivo más parecido a Python normal. Los type hints en Python y los tipos de Cython no tienen exactamente el mismo objetivo: unos ayudan al análisis; los otros pueden cambiar la representación y el rendimiento compilado.
Bucles numéricos
Un caso clásico consiste en recorrer arrays o buffers. Para NumPy, utiliza memoryviews tipadas:
import cython
@cython.boundscheck(False)
@cython.wraparound(False)
def sumar_array(double[:] valores):
cdef Py_ssize_t i
cdef double total = 0
for i in range(valores.shape[0]):
total += valores[i]
return totalDesactivar comprobaciones puede mejorar velocidad, pero un índice incorrecto puede provocar errores graves. Hazlo solo cuando las pruebas garanticen límites válidos.
Memoryviews
Las memoryviews permiten trabajar con memoria contigua de NumPy y otros objetos compatibles sin copiar necesariamente los datos. Pueden representar vectores y matrices con tipos concretos. Revisa dimensiones, orden y continuidad antes de asumir que una operación será rápida.
Liberar el GIL
El Global Interpreter Lock protege operaciones de objetos Python. Cython puede liberar el GIL en código que trabaja exclusivamente con tipos C:
cdef long calcular(int limite) noexcept nogil:
cdef int i
cdef long total = 0
for i in range(limite):
total += i * i
return totalNo puedes manipular listas, diccionarios o cadenas Python normales sin el GIL. Liberarlo no acelera automáticamente un único hilo; permite paralelismo cuando el trabajo compilado es adecuado.
prange para paralelismo
prange puede distribuir iteraciones con OpenMP:
from cython.parallel cimport prange
cdef long total = 0
cdef int i
for i in prange(limite, nogil=True):
# operación independiente
passConfigurar OpenMP varía por compilador. Además, las reducciones y escrituras compartidas deben diseñarse correctamente para evitar condiciones de carrera.
Integración con C
Cython puede importar declaraciones desde archivos .pxd y llamar bibliotecas C:
from libc.math cimport sqrt
cpdef double raiz(double valor):
return sqrt(valor)Esta capacidad resulta útil cuando existe una biblioteca madura que no tiene un wrapper Python.
Archivos .pxd
Los archivos .pxd funcionan de forma parecida a cabeceras: declaran tipos, funciones y clases de extensión. Separar interfaz e implementación mejora la reutilización entre módulos Cython.
La documentación oficial sobre archivos y compilación explica .pyx, .pxd, .pxi y opciones de construcción.
Generar un informe de anotación
Ejecuta:
cython -a calculos.pyxEl archivo HTML colorea líneas según la interacción con la API de Python. Las zonas con más amarillo suelen implicar más operaciones dinámicas. Este informe ayuda a decidir dónde añadir tipos.
Comparar rendimiento correctamente
Utiliza la misma entrada, repite ejecuciones y comprueba resultados. La guía sobre timeit en Python muestra cómo medir funciones pequeñas. Incluye también el costo de convertir datos si la versión Cython necesita preparar arrays.
Cython y NumPy
Antes de escribir bucles compilados, verifica si NumPy ya ofrece una operación vectorizada. Las bibliotecas numéricas ejecutan muchas operaciones en C y pueden ser más simples. Cython es especialmente útil cuando el algoritmo tiene lógica personalizada difícil de expresar con operaciones vectorizadas.
Empaquetado y distribución
Un paquete con extensiones compiladas necesita generar wheels para cada plataforma, versión de Python y arquitectura soportada. Los usuarios sin compilador pueden tener problemas si solo publicas una distribución fuente.
La gestión del proyecto con Poetry o herramientas basadas en pyproject.toml puede integrarse con el proceso, pero revisa la compatibilidad del backend de construcción.
Depuración
Mantén una versión Python de referencia y pruebas que comparen resultados. El código compilado puede fallar de maneras menos amigables si se desactivan comprobaciones. Empieza con seguridad completa, mide y desactiva controles únicamente en secciones verificadas.
Errores frecuentes
Los problemas comunes son compilar todo el proyecto sin perfilar, esperar mejoras sin añadir tipos, olvidar instalar un compilador, usar tipos demasiado pequeños, desactivar límites prematuramente y publicar sin wheels. También puede ocurrir que el cuello de botella esté en una base de datos o red, donde Cython no ayuda.
Buenas prácticas
Perfila primero, crea un benchmark, selecciona una función crítica, compílala sin alterar el comportamiento y añade tipos gradualmente. Conserva una interfaz Python sencilla, documenta los requisitos de construcción y automatiza pruebas en todas las plataformas soportadas.
Cython es una herramienta potente cuando se aplica a una parte bien identificada. No reemplaza algoritmos adecuados ni bibliotecas optimizadas, pero puede cerrar la distancia entre la productividad de Python y el rendimiento de C en cálculos específicos.






