Python mit C-Typen, kompiliert
Cython ist ein Compiler für eine Sprache, die Python plus optionale C-Typdeklarationen ist. Sie schreiben eine .pyx-Datei, Cython übersetzt sie nach C, ein C-Compiler macht daraus ein Erweiterungsmodul, und Sie importieren es wie jedes andere Modul. Schlichter, so kompilierter Python-Code läuft schon etwas schneller, der echte Gewinn kommt aber mit deklarierten Typen: cdef double total sagt Cython, dass total eine Maschinenzahl ist, kein Python-Objekt, sodass die Schleife auf rohen Zahlen ohne Interpreter-Overhead arbeitet. Schwere numerische Schleifen können fünfzig- bis hundertmal schneller werden, wie mit Numba, aber Sie erhalten ein gewöhnliches kompiliertes Modul, das sich ausliefern lässt.
# 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 functionTypen deklarieren: cdef, cpdef und Memoryviews
cdef deklariert eine C-Variable (cdef double total, cdef long i) oder eine Funktion, die nur C-Code aufrufen kann. cpdef deklariert eine Funktion, die von Python und von C aus aufrufbar ist, passend für die Einstiegspunkte eines Moduls. def bleibt eine gewöhnliche Python-Funktion. Arrays werden als typisierte Memoryviews übergeben, etwa double[:] für ein eindimensionales Array aus Doubles: Sie nehmen NumPy-Arrays ohne Kopie an und werden mit C-Geschwindigkeit indiziert. Eine Signatur wie cpdef double mean(double[:] x, long n) ersetzt also Pythons dynamische Typen durch exakte, und ein ausgelassener oder als object deklarierter Typ bleibt ein langsames Python-Objekt.
# 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)Das Modul bauen und herausfinden, was zu typisieren ist
Ein setup.py mit cythonize(["pricing.pyx"]) weist setuptools an, die Datei zu übersetzen und zu kompilieren; python setup.py build_ext --inplace erzeugt neben Ihrem Code eine Shared Library, und import pricing lädt sie. Für schnelle Experimente kompiliert pyximport beim Import. Um zu sehen, was noch langsam ist, führen Sie cython -a pricing.pyx aus: Es schreibt eine HTML-Seite, in der jede Zeile je nach genutzter Python-Maschinerie gelb schattiert ist, die weißen Zeilen sind reines C. Typisieren Sie die Variablen der gelben Zeilen in Ihren heißen Schleifen und schalten Sie dann Prüfungen, die Sie als unnötig erwiesen haben, mit @cython.boundscheck(False) und @cython.wraparound(False) ab, damit Indizierung so billig wird wie in 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 kompiliert eine
.pyx-Datei (Python plus optionale C-Typen) nach C und dann zu einem Erweiterungsmodul, das man normalimportiert. cdefdeklariert C-Variablen und reine C-Funktionen,cpdefeine von Python und C aufrufbare Funktion; Arrays sind typisierte Memoryviews wiedouble[:].- Bauen Sie mit
cythonizeinsetup.py, finden Sie mitcython -adie gelben (langsamen) Zeilen in heißen Schleifen und prüfen Sie gegen das einfache Python-Ergebnis.
Als Nächstes: C++ mit pybind11 aufrufen, vorhandenen C++-Code für Python verfügbar machen.
Lab: Was bewertet wird
- Python-Typen mit
c_typeauf C abbilden25 Pkt. - Mit
cdef_signatureeinen Funktionskopf schreiben25 Pkt. - Lokale Variablen mit
declare_localstypisieren25 Pkt. - Mit
build_setupdas Build-Skript schreiben25 Pkt.