For at understøtte objektorienteret programmering understøtter Cython skrivning af normalePython-klasser præcis som i Python:
class MathFunction(object): def __init__(self, name, operator): self.name = name self.operator = operator def __call__(self, *operands): return self.operator(*operands)
Baseret på det, som Python kalder en “indbygget type”, understøtter Cython dog en anden slags klasse: udvidelsestyper, som nogle gange kaldes “cdef-klasser” på grund af de nøgleord, der bruges til deres deklaration. De er noget begrænsede i forhold til Python-klasser, men er generelt mere hukommelseseffektive og hurtigere end generiske Python-klasser. Den vigtigste forskel er, at de bruger en C struct til at gemme deres felter og metoder i stedet for en Python dict. Dette giver dem mulighed for at lagre vilkårlige C-typer i deres felter uden at kræve en Python-wrapper for dem, og for at få adgang til felter og metoder direkte på C-niveau uden at gå gennem et Python-ordbogsopslag.
Normale Python-klasser kan arve fra cdef-klasser, men ikke omvendt. Cython kræver, at man skal kende det komplette arvehierarki for at kunne udlægge sine C-strukturer, og begrænser det til enkel arvegang. Normale Python-klasser kan derimod arve fra et vilkårligt antal Python-klasser og udvidelsestyper, både i Python-kode og i ren Python-kode.
Så vidt vides har vores integrationseksempel ikke været særlig nyttigt, da det kun integrerer en enkelt hårdt kodet funktion. For at afhjælpe dette, uden at det går ud over hastigheden, vil vi bruge en cdef-klasse til at repræsentere en funktion på flydende tal:
cdef class Function: cpdef double evaluate(self, double x) except *: return 0
Direktivet cpdef gør to versioner af metoden tilgængelige; en hurtig til brug fra Cython og en langsommere til brug fra Python. Derefter:
from libc.math cimport sincdef class Function: cpdef double evaluate(self, double x) except *: return 0cdef class SinOfSquareFunction(Function): cpdef double evaluate(self, double x) except *: return sin(x ** 2)
Dette gør lidt mere end at levere en python-wrapper til en cdefmetode: i modsætning til en cdef-metode er en cpdef-metode fuldt ud overstyrbar afmetode og instansattributter i Python-underklasser. Den tilføjer lidt opkaldsoverhead i forhold til en cdef-metode.
For at gøre klassedefinitionerne synlige for andre moduler og dermed muliggøre effektiv brug på C-niveau og arvelighed uden for det modul, der implementerer dem, definerer vi dem i en sin_of_square.pxd
fil:
cdef class Function: cpdef double evaluate(self, double x) except *cdef class SinOfSquareFunction(Function): cpdef double evaluate(self, double x) except *
Med dette kan vi nu ændre vores integrationseksempel:
from sin_of_square cimport Function, SinOfSquareFunctiondef integrate(Function f, double a, double b, int N): cdef int i cdef double s, dx if f is None: raise ValueError("f cannot be None") s = 0 dx = (b - a) / N for i in range(N): s += f.evaluate(a + i * dx) return s * dxprint(integrate(SinOfSquareFunction(), 0, 1, 10000))
Dette er næsten lige så hurtigt som den tidligere kode, men det er meget mere fleksibelt, da den funktion, der skal integreres, kan ændres. Vi kan endda indsætte en ny funktion defineret i Python-området:
>>> import integrate>>> class MyPolynomial(integrate.Function):... def evaluate(self, x):... return 2*x*x + 3*x - 10...>>> integrate(MyPolynomial(), 0, 1, 10000)-7.8335833300000077
Dette er ca. 20 gange langsommere, men stadig ca. 10 gange hurtigere end den oprindelige kode til integration udelukkende i Python. Det viser, hvor store hastighedsforøgelser der let kan opnås, når hele sløjfer flyttes fra Python-kode til et Cython-modul.
Nogle bemærkninger om vores nye implementering af evaluate
:
- Den hurtige metodeafsendelse her fungerer kun, fordi
evaluate
blev deklareret iFunction
. Hvisevaluate
var blevet indført iSinOfSquareFunction
, ville koden stadig fungere, men Cython ville i stedet have anvendt den langsommere Python-metodeafsendelsesmekanisme.- På samme måde ville den langsommere Python-afsendelse blive anvendt, hvis argumentet
f
ikke var blevet typet, men kun var blevet overgivet som et Python-objekt.- Da argumentet er typet, skal vi kontrollere, om det er
None
. I Python ville dette have resulteret i enAttributeError
, nårevaluate
-metoden blev slået op, men Cython ville i stedet forsøge at få adgang tilNone
s (inkompatible) interne struktur, som om det var enFunction
, hvilket ville føre til et nedbrud eller datakorruption.
Der findes et compilerdirektiv nonecheck
, som slår kontrollen af dette til, på bekostning af nedsat hastighed. Her er hvordan compilerdirektiver bruges til dynamisk at slå nonecheck
til eller fra dynamisk:
# cython: nonecheck=True# ^^^ Turns on nonecheck globallyimport cythoncdef class MyClass: pass# Turn off nonecheck locally for the [email protected](False)def func(): cdef MyClass obj = None try: # Turn nonecheck on again for a block with cython.nonecheck(True): print(obj.myfunc()) # Raises exception except AttributeError: pass print(obj.myfunc()) # Hope for a crash!
Attributter i cdef-klasser opfører sig anderledes end attributter i almindelige klasser:
- Alle attributter skal prædeklareres på kompileringstid
- Attributter er som standard kun tilgængelige fra Cython (typed access)
- Egenskaber kan deklareres for at udsætte dynamiske attributter for Python-rum
from sin_of_square cimport Functioncdef class WaveFunction(Function): # Not available in Python-space: cdef double offset # Available in Python-space: cdef public double freq # Available in Python-space, but only for reading: cdef readonly double scale # Available in Python-space: @property def period(self): return 1.0 / self.freq @period.setter def period(self, value): self.freq = 1.0 / value