Az objektumorientált programozás támogatása érdekében a Cython támogatja a normálPython osztályok írását, pontosan úgy, mint a Pythonban:
class MathFunction(object): def __init__(self, name, operator): self.name = name self.operator = operator def __call__(self, *operands): return self.operator(*operands)
A Python által “beépített típusnak” nevezett típusok alapján azonban a Cython támogat egy második típusú osztályt: a kiterjesztett típusokat, amelyeket néha “cdef osztályoknak” neveznek a deklarációjukhoz használt kulcsszavak miatt. Ezek a Python osztályokhoz képest némileg korlátozottak, de általában memóriatakarékosabbak és gyorsabbak, mint az általános Python osztályok. A legfontosabb különbség az, hogy a Python dict helyett C structot használnak a mezőik és metódusaik tárolására. Ez lehetővé teszi számukra, hogy tetszőleges C típusokat tároljanak a mezőikben anélkül, hogy Python wrapperre lenne szükségük, és hogy a mezőkhöz és metódusokhoz közvetlenül C szinten férjenek hozzá anélkül, hogy Python szótárkeresésen kellene keresztülmenniük.
A normál Python osztályok örökölhetnek cdef osztályokból, de fordítva nem. A Cython megköveteli a teljes öröklési hierarchia ismeretét a C-struktúráik elrendezéséhez, és korlátozza azt az egyszeres öröklésre. A normál Python-osztályok ezzel szemben tetszőleges számú Python-osztályból és bővítménytípusból örökölhetnek, mind aCython-kódban, mind a tiszta Python-kódban.
Az integrációs példánk eddig nem volt túl hasznos, mivel csak egyetlen keményen kódolt függvényt integrál. Ennek orvoslására,a sebesség alig csökkenő feláldozása mellett, egy cdef osztályt fogunk használni a lebegőpontos számokra vonatkozó függvény reprezentálására:
cdef class Function: cpdef double evaluate(self, double x) except *: return 0
A cpdef direktíva a metódus két változatát teszi elérhetővé; egy gyorsat a Cythonból való használatra és egy lassabbat a Pythonból való használatra. Akkor:
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)
Ez valamivel többet tesz, mint egy python wrapper biztosítása egy cdefmetódus számára: a cdef metódustól eltérően a cpdef metódus teljesen felülírható aMódszerek és példányattribútumok a Python alosztályokban. A cdef metódushoz képest némi hívási többletköltséget jelent.
Hogy az osztálydefiníciókat más modulok számára is láthatóvá tegyük, és így lehetővé tegyük a hatékony C-szintű használatot és öröklést az őket implementáló modulon kívül, egy sin_of_square.pxd
fájlban definiáljuk őket:
cdef class Function: cpdef double evaluate(self, double x) except *cdef class SinOfSquareFunction(Function): cpdef double evaluate(self, double x) except *
Ezt felhasználva most megváltoztathatjuk integrációs példánkat:
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))
Ez majdnem olyan gyors, mint az előző kód, azonban sokkal rugalmasabb, mivel az integrálandó függvény megváltoztatható. Akár egy új, Python-térben definiált függvényt is átadhatunk:
>>> import integrate>>> class MyPolynomial(integrate.Function):... def evaluate(self, x):... return 2*x*x + 3*x - 10...>>> integrate(MyPolynomial(), 0, 1, 10000)-7.8335833300000077
Ez körülbelül 20-szor lassabb, de még mindig körülbelül 10-szer gyorsabb, mint az eredeti, csak Pythonra épülő integrációs kód. Ez mutatja, hogy milyen nagy lehet a sebességnövekedés, ha egész ciklusokat helyezünk át a Python kódból egy Cython modulba.
Néhány megjegyzés a evaluate
új implementációjáról:
- A gyors metóduskiosztás itt csak azért működik, mert a
evaluate
aFunction
-ben lett deklarálva. Haevaluate
aSinOfSquareFunction
-ban lett volna bevezetve, a kód még mindig működne, de a Cython a lassabb Python metódus-diszpatch mechanizmust használta volna helyette.- Hasonlóképpen, ha az argumentum
f
nem lett volna tipizálva, hanem csak Python objektumként lett volna átadva, a lassabb Python diszpatchet használnánk.- Mivel az argumentum tipizálva van, ellenőriznünk kell, hogy az
None
. Pythonban ez egyAttributeError
t eredményezett volna, amikor aevaluate
metódust kerestük, de a Cython ehelyett megpróbálna hozzáférni aNone
(nem kompatibilis) belső struktúrájához, minthaFunction
lenne, ami összeomláshoz vagy adatsérüléshez vezetne.
Létezik egy nonecheck
fordítói direktíva, amely bekapcsolja az erre vonatkozó ellenőrzést, a sebesség csökkenésének árán. Íme, hogyan használjuk a fordítói direktívákat a nonecheck
dinamikus be- vagy kikapcsolására:
# 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!
A cdef osztályokban lévő attribútumok másképp viselkednek, mint a normál osztályokban lévő attribútumok:
- Minden attribútumot előre kell deklarálni fordítási időben
- Az attribútumok alapértelmezés szerint csak a Cythonból érhetők el (tipizált hozzáférés)
- A tulajdonságok deklarálhatók, hogy a dinamikus attribútumokat a Python-térbe tárják
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
.