Python con tipos de C, compilado
Cython es un compilador para un lenguaje que es Python más declaraciones de tipos de C opcionales. Escribes un archivo .pyx, Cython lo traduce a C, un compilador de C lo convierte en un módulo de extensión y lo importas como cualquier otro módulo. El código Python simple compilado así ya va algo más rápido, pero la ganancia real llega al declarar tipos: cdef double total le dice a Cython que total es un número de máquina, no un objeto de Python, de modo que el bucle trabaja con números crudos sin sobrecarga del intérprete. Los bucles numéricos pesados pueden volverse de cincuenta a cien veces más rápidos, como con Numba, pero obtienes un módulo compilado normal que puedes distribuir.
# What a Cython file looks like: Python with cdef type declarations (shown here as text)
pyx = """\
cpdef double dot(double[:] a, double[:] b):
cdef double total = 0.0
cdef long i
for i in range(a.shape[0]):
total += a[i] * b[i]
return total
"""
print(pyx.splitlines()[0]) # cpdef double dot(double[:] a, double[:] b):
print(sum("cdef" in line for line in pyx.splitlines())) # 2 two typed declarations inside the functionDeclarar tipos: cdef, cpdef y vistas de memoria
cdef declara una variable de C (cdef double total, cdef long i) o una función que solo el código C puede llamar. cpdef declara una función invocable desde Python y desde C, lo que conviene a los puntos de entrada de un módulo. def sigue siendo una función normal de Python. Los arrays se pasan como vistas de memoria tipadas, como double[:] para un array unidimensional de doubles: aceptan arrays de NumPy sin copiar y se indexan a la velocidad de C. Una firma como cpdef double mean(double[:] x, long n) sustituye así los tipos dinámicos de Python por tipos exactos, y un tipo omitido, o declarado object, sigue siendo un objeto lento de Python.
# Building the declarations as text: one table from Python type names to C types
C_TYPES = {"int": "long", "float": "double", "bool": "bint", "array[float]": "double[:]"}
def c_type(py_type):
return C_TYPES.get(py_type, "object")
def cdef_signature(name, params, returns):
result = "void" if returns is None else c_type(returns)
arguments = ", ".join(f"{c_type(t)} {p}" for p, t in params)
return f"cpdef {result} {name}({arguments})"
print(cdef_signature("mean", [("x", "array[float]"), ("n", "int")], "float")) # cpdef double mean(double[:] x, long n)
print(cdef_signature("log_event", [("message", "dict")], None)) # cpdef void log_event(object message)Construir el módulo y encontrar qué tipar
Un setup.py con cythonize(["pricing.pyx"]) indica a setuptools que traduzca y compile el archivo; python setup.py build_ext --inplace produce una biblioteca compartida junto a tu código y import pricing la carga. Para pruebas rápidas, pyximport compila al importar. Para ver qué sigue lento, ejecuta cython -a pricing.pyx: escribe una página HTML donde cada línea se sombrea de amarillo según la cantidad de maquinaria de Python que usa, y las líneas blancas son C puro. Tipa las variables de las líneas amarillas dentro de tus bucles críticos y luego desactiva las comprobaciones que hayas probado innecesarias con @cython.boundscheck(False) y @cython.wraparound(False) para que indexar cueste tan poco como en C.
# The build script is a few lines of text; this generates it for a list of modules
def build_setup(modules):
files = ", ".join(f'"{module}.pyx"' for module in modules)
return "from setuptools import setup\nfrom Cython.Build import cythonize\n\n" + f"setup(ext_modules=cythonize([{files}]))\n"
print(build_setup(["pricing", "risk"]), end="")
# from setuptools import setup
# from Cython.Build import cythonize
#
# setup(ext_modules=cythonize(["pricing.pyx", "risk.pyx"]))# The loop a .pyx file would hold, as plain Python: same result, compiled once types are added
def dot(a, b):
total = 0.0
for i in range(len(a)):
total += a[i] * b[i]
return total
prices = [184.25, 410.5, 99.0]
quantities = [100.0, 50.0, 200.0]
print(dot(prices, quantities)) # 58750.0 the compiled version returns exactly this number
print(dot(prices, quantities) == sum(p * q for p, q in zip(prices, quantities))) # True- Cython compila un archivo
.pyx(Python más tipos de C opcionales) a C y luego a un módulo de extensión que seimportnormalmente. cdefdeclara variables de C y funciones solo de C,cpdefuna función invocable desde Python y C; los arrays son vistas de memoria tipadas comodouble[:].- Construye con
cythonizeensetup.py, usacython -apara hallar las líneas amarillas (lentas) en bucles críticos y verifica con el resultado de Python simple.
A continuación: llamar a C++ con pybind11, exponer código C++ existente a Python.
Lab: qué se califica
- Asociar tipos de Python y de C con
c_type25 pts - Escribir la cabecera de una función con
cdef_signature25 pts - Tipar las variables locales con
declare_locals25 pts - Escribir el script de compilación con
build_setup25 pts