För att stödja objektorienterad programmering stöder Cython att skriva normalaPython-klasser precis 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)
Baserat på det som Python kallar för en ”inbyggd typ” stöder Cython dock en andra typ av klasser: utvidgningstyper, ibland kallade ”cdef-klasser” på grund av de nyckelord som används för att deklarera dem. De är något begränsade jämfört med Pythonklasser, men är i allmänhet mer minneseffektiva och snabbare än generiska Pythonklasser. Den största skillnaden är att de använder en C struct för att lagra sina fält och metoder i stället för en Python dict. Detta gör att de kan lagra godtyckliga C-typer i sina fält utan att det krävs en Python-omslagare för dem, och att de kan få tillgång till fält och metoder direkt på C-nivå utan att gå igenom en Python-ordboksökning.
Normala Python-klasser kan ärva från cdef-klasser, men inte tvärtom. Cython kräver att man känner till hela arvshierarkin för att kunna lägga ut sina C-strukturer, och begränsar det till enkel arv. Normala Pythonklasser kan däremot ärva från ett obegränsat antal Pythonklasser och utvidgningstyper, både i Pythonkod och i ren Pythonkod.
Hos vårt integrationsexempel har hittills inte varit särskilt användbart eftersom det bara integrerar en enda hårdkodad funktion. För att råda bot på detta,utan att offra hastigheten, kommer vi att använda en cdef-klass för att representera en funktion på flyttal:
cdef class Function: cpdef double evaluate(self, double x) except *: return 0
Direktivet cpdef gör två versioner av metoden tillgängliga; en snabb för användning från Cython och en långsammare för användning från Python. Sedan:
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)
Detta gör något mer än att tillhandahålla en Python-omslag för en cdefmetod: till skillnad från en cdef-metod kan en cpdef-metod helt och hållet överskridas avmetoder och instansattribut i Python-underklasser. Den lägger till en liten anropsöverhead jämfört med en cdef-metod.
För att göra klassdefinitionerna synliga för andra moduler och på så sätt möjliggöra effektiv användning på C-nivå och arv utanför den modul som implementerar dem, definierar 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 hjälp av detta kan vi nu ändra vårt integrationsexempel:
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))
Detta är nästan lika snabbt som den tidigare koden, men det är mycket mer flexibelt eftersom funktionen att integrera kan ändras. Vi kan till och med skicka in en ny funktion definierad i Python-space:
>>> import integrate>>> class MyPolynomial(integrate.Function):... def evaluate(self, x):... return 2*x*x + 3*x - 10...>>> integrate(MyPolynomial(), 0, 1, 10000)-7.8335833300000077
Detta är ungefär 20 gånger långsammare, men fortfarande ungefär 10 gånger snabbare än den ursprungliga koden för integrering med enbart Python. Detta visar hur stora snabbhetsökningar som lätt kan bli när hela slingor flyttas från Python-kod till en Cython-modul.
Några anteckningar om vår nya implementering av evaluate
:
- Den snabba metodavsändningen här fungerar bara för att
evaluate
deklarerades iFunction
. Omevaluate
hade införts iSinOfSquareFunction
skulle koden fortfarande fungera, men Cython skulle i stället ha använt den långsammare Python-metodavsändningsmekanismen.- På samma sätt skulle den långsammare Python-avsändningsmekanismen ha använts om argumentet
f
inte hade typats, utan bara hade överlämnats som ett Python-objekt.- Då argumentet är typat måste vi kontrollera om det är
None
. I Python skulle detta ha resulterat i ettAttributeError
närevaluate
-metoden söktes upp, men Cython skulle i stället försöka få tillgång tillNone
s (inkompatibla) interna struktur som om det var enFunction
, vilket skulle leda till en krasch eller datakorruption.
Det finns ett kompilardirektiv nonecheck
som aktiverar kontrollerna för detta, till priset av minskad hastighet. Så här används kompilerdirektiv för att dynamiskt slå på eller av nonecheck
:
# 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!
Attribut i cdef-klasser beter sig annorlunda än attribut i vanliga klasser:
- Alla attribut måste fördeklareras vid kompilering
- Attributen är som standard endast åtkomliga från Cython (typad åtkomst)
- Det går att deklarera egenskaper för att exponera dynamiska attribut för Python-utrymmet
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