Para suportar programação orientada a objetos, Cython suporta escrever classes normalPython exatamente como em Python:
class MathFunction(object): def __init__(self, name, operator): self.name = name self.operator = operator def __call__(self, *operands): return self.operator(*operands)
Baseado no que Python chama de “built-in type”, no entanto, Cython suporta um segundo tipo de classe: tipos de extensão, às vezes chamados de “classes cdef” devido às palavras-chave usadas para sua declaração. Elas são um pouco restritas comparadas às classes Python, mas geralmente são mais eficientes em termos de memória e mais rápidas do que as classes Python genéricas. A principal diferença é que eles usam uma estrutura em C para armazenar seus campos e métodos em vez de uma dita Python. Isto permite-lhes armazenar tipos arbitrários de C em seus campos sem requerer um wrapper Python para eles, e acessar campos e métodos diretamente no nível C sem passar por uma busca no dicionário Python.
Classes normais Python podem herdar das classes cdef, mas não o contrário. Cython requer conhecer a hierarquia completa da herança para poder traçar as suas estruturas em C, e restringe a herança. Classes Python normais, por outro lado, caninherit de qualquer número de classes Python e tipos de extensão, ambos código inCython e código Python puro.
Até agora nosso exemplo de integração não tem sido muito útil, pois ele só integra uma única função codificada. A fim de remediar isso, com pouca velocidade, usaremos uma classe cdef para representar a função em números de ponto flutuante:
cdef class Function: cpdef double evaluate(self, double x) except *: return 0
A diretiva cpdef disponibiliza duas versões do método; uma mais rápida para uso do Cython e outra mais lenta para uso do Python. Então:
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)
Isto faz um pouco mais do que fornecer um invólucro python para um método cdef: ao contrário de um método cdef, um método cpdef é totalmente substituível por bymethods e atributos de instância nas subclasses Python. Ele adiciona um pouco de sobrecarga de chamada em comparação com um método cdef.
Para tornar as definições de classe visíveis para outros módulos, e assim permitir o uso eficiente do nível C e sua herança fora do módulo, nós os definimos em um arquivo 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 *
Usando isto, podemos agora mudar o nosso exemplo de integração:
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))
Isto é quase tão rápido como o código anterior, no entanto é muito mais flexível uma vez que a função a integrar pode ser alterada. Podemos até passar em uma nova função definida no 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
Esta é cerca de 20 vezes mais lenta, mas ainda assim cerca de 10 vezes mais rápida do que o código original de integração apenas em Python. Isto mostra o quão grande pode ser a velocidade dos loops inteiros podem ser facilmente movidos do código Python para um módulo Cython.
Algumas notas sobre a nossa nova implementação de evaluate
:
- O envio rápido do método aqui só funciona porque
evaluate
foi declarado emFunction
. Seevaluate
tivesse sido introduzido emSinOfSquareFunction
, o código ainda funcionaria, mas Cython teria usado o mecanismo de despacho mais lento do método Python em vez disso.- Da mesma forma, se o argumento
f
não tivesse sido digitado, mas apenas passado como um objeto Python, o despacho Python mais lento teria sido usado.- Desde que o argumento seja digitado, precisamos verificar se ele é
None
. Em Python, isto teria resultado numAttributeError
quando o métodoevaluate
foi pesquisado, mas Cython em vez disso tentaria acessar a estrutura interna (incompatível) deNone
como se fosse umFunction
, levando a um crash ou corrupção de dados.
Existe uma diretiva de compilação nonecheck
que liga as verificações para isto, ao custo de uma velocidade reduzida. Aqui está como as diretrizes do compilador são usadas para ligar ou desligar dinamicamente 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!
Atributos em classes cdef se comportam de forma diferente dos atributos em classes regulares:
- Todos os atributos devem ser pré-declarados em tempo de compilação
- Os atributos são por padrão acessíveis apenas a partir do Cython (acesso digitado)
- As propriedades podem ser declaradas para expor atributos dinâmicos ao 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