Pro podporu objektově orientovaného programování podporuje Cython zápis běžných tříd Pythonu přesně tak, jak je tomu v Pythonu:
class MathFunction(object): def __init__(self, name, operator): self.name = name self.operator = operator def __call__(self, *operands): return self.operator(*operands)
Na základě toho, co Python nazývá „vestavěný typ“, však Cython podporujedruhý druh tříd: rozšiřující typy, někdy označované jako „cdef classes“ kvůli klíčovým slovům používaným pro jejich deklaraci. Ve srovnání s třídami Pythonu jsou poněkud omezené, ale obecně jsou paměťově úspornější a rychlejší než obecné třídy Pythonu. Hlavní rozdíl spočívá v tom, že pro ukládání svých polí a metod používají struct jazyka C namísto dict jazyka Python. To jim umožňuje ukládat ve svých polích libovolné typy jazyka C, aniž by pro ně vyžadovaly obal jazyka Python, a přistupovat k polím a metodám přímo na úrovni jazyka C bez nutnosti vyhledávání ve slovníku jazyka Python.
Normální třídy jazyka Python mohou dědit z tříd cdef, ale ne naopak. Cython vyžaduje znát kompletní hierarchii dědičnosti, aby mohl rozvrhnout své struktury v jazyce C, a omezuje ji na dědičnost po jednom. Normální třídy Pythonu naproti tomu mohoudědit z libovolného počtu tříd Pythonu a rozšiřujících typů, a to jak v kóduCythonu, tak v čistém kódu Pythonu.
Náš příklad integrace zatím nebyl příliš užitečný, protože integroval pouze jedinou pevně zakódovanou funkci. Abychom to napravili,a přitom téměř neobětovali rychlost, použijeme třídu cdef, která bude reprezentovat funkci na čísla s pohyblivou řádovou čárkou:
cdef class Function: cpdef double evaluate(self, double x) except *: return 0
Direktivou cpdef zpřístupníme dvě verze metody; jednu rychlou pro použití z Cythonu a jednu pomalejší pro použití z Pythonu. Pak:
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)
Tato funkce dělá o něco víc než jen to, že poskytuje pythonovský obal pro cdefmetodu: na rozdíl od cdef metody je cpdef metoda plně přepisovatelná metodami a atributy instancí v podtřídách Pythonu. Ve srovnání s metodou cdef přidává malou režii volání.
Aby definice tříd byly viditelné pro ostatní moduly, a umožnily tak efektivní použití a dědičnost na úrovni C mimo modul, který je implementuje, definujeme je v sin_of_square.pxd
souboru:
cdef class Function: cpdef double evaluate(self, double x) except *cdef class SinOfSquareFunction(Function): cpdef double evaluate(self, double x) except *
Pomocí toho můžeme nyní změnit náš integrační příklad:
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))
Tento postup je téměř stejně rychlý jako předchozí kód, je však mnohem flexibilnější, protože funkci, která se má integrovat, lze změnit. Dokonce můžeme předat novou funkci definovanou v Pythonu-prostoru:
>>> import integrate>>> class MyPolynomial(integrate.Function):... def evaluate(self, x):... return 2*x*x + 3*x - 10...>>> integrate(MyPolynomial(), 0, 1, 10000)-7.8335833300000077
Toto je asi 20krát pomalejší, ale stále asi 10krát rychlejší než původní integrační kód pouze v Pythonu. To ukazuje, jak velké zrychlení může snadno nastat, když se celé smyčky přesunou z kódu Pythonu do modulu Cythonu.
Několik poznámek k naší nové implementaci evaluate
:
- Rychlé odesílání metod zde funguje jen proto, že
evaluate
byla deklarována vFunction
. Kdyby bylevaluate
zaveden vSinOfSquareFunction
, kód by stále fungoval, ale Cython by místo toho použil pomalejší mechanismus dispečinku metod Pythonu.- Stejným způsobem, kdyby argument
f
nebyl typován, ale byl předán pouze jako objekt Pythonu, použil by se pomalejší dispečink Pythonu.- Protože je argument typován, musíme zkontrolovat, zda je
None
. V jazyce Python by to mělo za následekAttributeError
při vyhledávání metodyevaluate
, ale Cython by se místo toho snažil přistupovat k (nekompatibilní) vnitřní struktuřeNone
, jako by to bylFunction
, což by vedlo k pádu nebo poškození dat.
Existuje direktiva kompilátoru nonecheck
, která tuto kontrolu zapíná za cenu snížení rychlosti. Zde je uvedeno, jak se směrnice překladačepoužívají k dynamickému zapnutí nebo vypnutí 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!
Atributy ve třídách cdef se chovají jinak než atributy v běžných třídách:
- Všechny atributy musí být předem deklarovány v době kompilace
- Atributy jsou ve výchozím nastavení přístupné pouze z Cythonu (typizovaný přístup)
- Vlastnosti mohou být deklarovány tak, aby vystavovaly dynamické atributy do Python-space
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
.