Tipuri de extensii (aka. cdef classes)¶

Pentru a sprijini programarea orientată pe obiecte, Cython suportă scrierea claselor normalePython exact ca în Python:

class MathFunction(object): def __init__(self, name, operator): self.name = name self.operator = operator def __call__(self, *operands): return self.operator(*operands)

Bazat pe ceea ce Python numește „tip încorporat”, Cython suportă totușiun al doilea tip de clasă: tipurile de extensie, denumite uneori „clase cdef” datorită cuvintelor cheie utilizate pentru declararea lor. Acestea sunt oarecum limitate în comparație cu clasele Python, dar sunt în general mai eficiente din punct de vedere al memoriei și mai rapide decât clasele Python generice. Principala diferență constă în faptul că acestea utilizează un struct C pentru a-și stoca câmpurile și metodele în loc de un dict Python. Acest lucru le permite să stocheze tipuri C arbitrare în câmpurile lor fără a avea nevoie de un înveliș Python pentru acestea și să acceseze câmpurile și metodele direct la nivelul C fără a trece printr-o căutare în dicționarul Python.

Classele Python normale pot moșteni din clasele cdef, dar nu și invers. Cython cere să cunoască întreaga ierarhie a moștenirii pentru a-și așeza structurile C, și o limitează la moștenirea simplă. Clasele Python normale, pe de altă parte, pot moșteni din orice număr de clase Python și tipuri de extensii, atât în codul Cython, cât și în codul Python pur.

Până acum, exemplul nostru de integrare nu a fost foarte util, deoarece integrează doar o singură funcție codificată. Pentru a remedia acest lucru,fără a sacrifica aproape deloc viteza, vom folosi o clasă cdef pentru a reprezenta o funcție pe numere cu virgulă mobilă:

cdef class Function: cpdef double evaluate(self, double x) except *: return 0

Directiva cpdef face disponibile două versiuni ale metodei; una rapidă pentru a fi folosită din Cython și una mai lentă pentru a fi folosită din Python. Apoi:

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)

Aceasta face ceva mai mult decât să ofere un înveliș python pentru o metodă cdef: spre deosebire de o metodă cdef, o metodă cpdef este complet suprascriptibilă prin metode și atribute de instanță în subclasele Python. Aceasta adaugă o mică suprataxă de apelare în comparație cu o metodă cdef.

Pentru ca definițiile claselor să fie vizibile pentru alte module, permițând astfel utilizarea eficientă la nivel C și moștenirea în afara modulului care le implementează, le definim într-un fișier sin_of_square.pxd:

cdef class Function: cpdef double evaluate(self, double x) except *cdef class SinOfSquareFunction(Function): cpdef double evaluate(self, double x) except *

Utilizând acest lucru, putem modifica acum exemplul nostru de integrare:

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))

Acest lucru este aproape la fel de rapid ca și codul anterior, însă este mult mai flexibil, deoarece funcția de integrat poate fi schimbată. Putem chiar să trecem o nouă funcție definită în 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

Acest lucru este de aproximativ 20 de ori mai lent, dar totuși de aproximativ 10 ori mai rapid decât codul original de integrare doar în Python. Acest lucru arată cât de mari pot fi cu ușurință creșterile de viteză atunci când bucle întregi sunt mutate din codul Python într-un modul Cython.

Câteva note despre noua noastră implementare a lui evaluate:

  • Expedierea rapidă a metodei de aici funcționează doar pentru că evaluate a fost declarată în Function. Dacă evaluate ar fi fost introdus înSinOfSquareFunction, codul ar fi funcționat în continuare, dar Cython ar fi folosit în schimb mecanismul mai lent de dispecerizare a metodelor Python.
  • În același mod, dacă argumentul f nu ar fi fost tipizat, ci doar transmis ca obiect Python, s-ar fi folosit dispecerizarea Python mai lentă.
  • Din moment ce argumentul este tipizat, trebuie să verificăm dacă esteNone. În Python, acest lucru ar fi avut ca rezultat un AttributeErrorla căutarea metodei evaluate, dar Cython ar fi încercat în schimb să acceseze structura internă (incompatibilă) a lui None ca și cum ar fi fost un Function, ceea ce ar fi dus la un blocaj sau la o corupere a datelor.

Există o directivă de compilare nonecheck care activează verificările pentru acest lucru, cu prețul unei viteze reduse. Iată cum se folosesc directivele compilatorului pentru a activa sau dezactiva în mod dinamic 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!

Atributele din clasele cdef se comportă diferit de atributele din clasele obișnuite:

  • Toate atributele trebuie să fie pre-declarate la compilare
  • Atributele sunt implicit accesibile doar din Cython (acces tipizat)
  • Proprietățile pot fi declarate pentru a expune atributele dinamice în spațiul Python
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

.

Lasă un comentariu