Python com tipos C, compilado
Cython é um compilador para uma linguagem que é Python mais declarações opcionais de tipos C. Você escreve um arquivo .pyx, o Cython o traduz para C, um compilador C o transforma em um módulo de extensão, e você o importa como qualquer outro módulo. Código Python simples compilado assim já roda um pouco mais rápido, mas o ganho real vem ao declarar tipos: cdef double total diz ao Cython que total é um número de máquina, não um objeto Python, de modo que o laço trabalha com números crus sem custo do interpretador. Laços numéricos pesados podem ficar de cinquenta a cem vezes mais rápidos, como com o Numba, mas você obtém um módulo compilado comum que pode 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 e views de memória
cdef declara uma variável C (cdef double total, cdef long i) ou uma função que só código C pode chamar. cpdef declara uma função chamável de Python e de C, o que serve aos pontos de entrada de um módulo. def continua sendo uma função Python comum. Arrays são passados como views de memória tipadas, como double[:] para um array unidimensional de doubles: aceitam arrays do NumPy sem cópia e são indexadas na velocidade do C. Uma assinatura como cpdef double mean(double[:] x, long n) troca assim os tipos dinâmicos do Python por tipos exatos, e um tipo omitido, ou declarado object, continua sendo um objeto Python lento.
# 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 o módulo e descobrir o que tipar
Um setup.py com cythonize(["pricing.pyx"]) diz ao setuptools para traduzir e compilar o arquivo; python setup.py build_ext --inplace produz uma biblioteca compartilhada ao lado do seu código, e import pricing a carrega. Para testes rápidos, o pyximport compila na importação. Para ver o que ainda é lento, rode cython -a pricing.pyx: ele escreve uma página HTML em que cada linha é sombreada de amarelo conforme a quantidade de maquinaria Python que usa, e as linhas brancas são C puro. Tipe as variáveis das linhas amarelas dentro dos laços críticos e depois desligue as verificações que você provou desnecessárias com @cython.boundscheck(False) e @cython.wraparound(False) para que indexar custe tão pouco quanto em 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- O Cython compila um arquivo
.pyx(Python mais tipos C opcionais) para C e depois para um módulo de extensão que se importa normalmente. cdefdeclara variáveis C e funções só de C,cpdefuma função chamável de Python e C; arrays são views de memória tipadas comodouble[:].- Construa com
cythonizenosetup.py, usecython -apara achar as linhas amarelas (lentas) em laços críticos e verifique com o resultado do Python simples.
A seguir: chamar C++ com pybind11, expor código C++ existente ao Python.
Lab: o que é avaliado
- Mapear tipos Python para C com
c_type25 pts - Escrever o cabeçalho de uma função com
cdef_signature25 pts - Tipar as variáveis locais com
declare_locals25 pts - Escrever o script de build com
build_setup25 pts