Tukeakseen oliosuuntautunutta ohjelmointia Cython tukee normaalienPython-luokkien kirjoittamista täsmälleen kuten Pythonissa:
class MathFunction(object): def __init__(self, name, operator): self.name = name self.operator = operator def __call__(self, *operands): return self.operator(*operands)
Perustuen siihen, mitä Python kutsuu ”sisäänrakennetuksi tyypiksi”, Cython tukee kuitenkin toista tyyppiä luokkia: laajennustyyppejä eli extension-tyyppejä (extension types), joita kutsutaan toisinaan myös nimelläCdef-luokkia (cdef-luokkia), johtuen avainsanasta, jota käytetään niiden ilmoittamiseen. Ne ovat jonkin verran rajoitetumpia kuin Python-luokat, mutta ovat yleensä muistitehokkaampia ja nopeampia kuin yleiset Python-luokat. Tärkein ero on, että ne käyttävät kenttiensä ja metodiensa tallentamiseen C structia Python dictin sijasta. Tämä mahdollistaa sen, että ne voivat tallentaa mielivaltaisia C-tyyppejä kenttiinsä ilman, että niille tarvitaan Python-kääre, ja että kenttiä ja metodeja voidaan käyttää suoraan C-tasolla ilman Python-sanakirjahakua.
Normaalit Python-luokat voivat periytyä cdef-luokista, mutta ei päinvastoin. Cython vaatii tuntemaan täydellisen periytymishierarkian voidakseen asettaa C-rakenteensa, ja rajoittaa sen pelkkään periytymiseen. Normaalit Python-luokat voivat sen sijaan periytyä mistä tahansa Python-luokista ja laajennustyypeistä sekä Cython-koodissa että puhtaassa Python-koodissa.
Tähän mennessä integraatioesimerkkimme ei ole ollut kovin käyttökelpoinen, koska se integroi vain yhden kovakoodatun funktion. Korjataksemme tämän,tuskin nopeudesta tinkimättä, käytämme cdef-luokkaa edustamaanfunktiota liukuluvuilla:
cdef class Function: cpdef double evaluate(self, double x) except *: return 0
Direktiivi cpdef tekee metodista kaksi versiota saataville; yhden nopean Cythonista käytettäväksi ja yhden hitaamman Pythonista käytettäväksi. Sitten:
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)
Tämä tekee hieman enemmän kuin tarjoaa python-kääreen cdef-metodille: toisin kuin cdef-metodi, cpdef-metodi on täysin korvattavissa Pythonin alaluokkien metodeilla ja instanssiattribuuteilla. Se lisää hieman soittamisen yleiskustannuksia verrattuna cdef-metodiin.
Tehdäksemme luokkamäärittelyt näkyviksi muille moduuleille ja mahdollistaaksemme siten tehokkaan C-tason käytön ja periytymisen niitä toteuttavan moduulin ulkopuolella, määrittelemme ne sin_of_square.pxd
-tiedostossa:
cdef class Function: cpdef double evaluate(self, double x) except *cdef class SinOfSquareFunction(Function): cpdef double evaluate(self, double x) except *
Tämän avulla voimme nyt muuttaa integrointiesimerkkiämme:
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))
Tämä on melkein yhtä nopeaa kuin edellinen koodi, mutta paljon joustavampaa, koska integroitavaa funktiota voidaan muuttaa. Voimme jopa välittää Python-avaruudessa määritellyn uuden funktion:
>>> import integrate>>> class MyPolynomial(integrate.Function):... def evaluate(self, x):... return 2*x*x + 3*x - 10...>>> integrate(MyPolynomial(), 0, 1, 10000)-7.8335833300000077
Tämä on noin 20 kertaa hitaampi, mutta silti noin 10 kertaa nopeampi kuin alkuperäinen, vain Pythonilla integroitava koodi. Tämä osoittaa, kuinka suuri nopeuslisäys voi helposti olla, kun kokonaisia silmukoita siirretään Python-koodista Cython-moduuliin.
Joitakin huomioita uudesta evaluate
-toteutuksestamme:
- Nopea metodin lähetys tässä toimii vain siksi, että
evaluate
olijulistettuFunction
:ssa. Josevaluate
olisi otettu käyttöönSinOfSquareFunction
:ssä, koodi toimisi silti, mutta Cython olisi käyttänyt sen sijaan hitaampaa Python-metodin dispatch-mekanismia.- Samoin, jos argumenttia
f
ei olisi tyypitetty, vaan se olisi vain luovutettu Python-oliona, käytettäisiin hitaampaa Python-dispatch-mekanismia.- Koska argumentti on tyypitetty, on tarkistettava, onko se
None
. Pythonissa tämä olisi johtanutAttributeError
ilmoitukseen, kunevaluate
-metodia etsitään, mutta Cython sen sijaan yrittäisi käyttääNone
:n (epäyhteensopivaa) sisäistä rakennettaNone
ikään kuin se olisiFunction
, mikä johtaisi kaatumiseen tai datan korruptoitumiseen.
Es on olemassa kääntäjädirektiivi nonecheck
, joka kytkee tarkistukset päälle nopeuden heikkenemisen kustannuksella. Seuraavassa kerrotaan, miten kääntäjän direktiivejä käytetään 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!
Cdef-luokkien attribuutit käyttäytyvät eri tavalla kuin tavallisten luokkien attribuutit:
- Kaikki attribuutit on ilmoitettava valmiiksi kääntämisaikana
- Attribuutteihin pääsee oletusarvoisesti käsiksi vain Cythonista käsin (typed access)
- Ominaisuudet voidaan ilmoittaa, jotta dynaamiset attribuutit voidaan paljastaa Python-avaruuteen
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
.