Bővítménytípusok (más néven. cdef osztályok)¶

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 a Function-ben lett deklarálva. Ha evaluate 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 egy AttributeErrort eredményezett volna, amikor a evaluate metódust kerestük, de a Cython ehelyett megpróbálna hozzáférni a None (nem kompatibilis) belső struktúrájához, mintha Function lenne, ami összeomláshoz vagy adatsérüléshez vezetne.

Létezik egy nonecheckfordí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

.

Szólj hozzá!