Extension types (aka. cdef classes). cdef クラス)¶

オブジェクト指向プログラミングをサポートするために、Cython は通常の Python クラスを Python と同じように書くことをサポートしています:

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

Python が “built-in type” と呼ぶものに基づいて、しかし Cython では 2 種類のクラス、拡張タイプ、時には宣言用のキーワードにより “cdef class” として呼ばれる、をサポートします。 これらはPythonのクラスと比べるとやや制限されますが、一般にPythonの汎用クラスよりもメモリ効率がよく、高速です。 主な違いは、Pythonのdictの代わりにCの構造体を使用してフィールドとメソッドを格納することです。

通常の Python クラスは cdef クラスを継承することができますが、その逆はできません。 Cython は C 言語の構造体をレイアウトするために、完全な継承階層を知る必要があり、単一継承に制限しています。 一方、通常の Python クラスは、Cython コードと純粋な Python コードの両方で、任意の数の Python クラスと拡張型から継承することができます。

これまでの統合例は、ハードコードされた 1 つの関数を統合しているだけなので、あまり役に立ちませんでした。

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

指示 cpdef はメソッドの2つのバージョンを利用できるようにします:一つは Cython から使うための速いバージョン、もう一つは Python から使うための遅いバージョンです。 cdef メソッドとは異なり、cpdef メソッドは Python のサブクラスでメソッドとインスタンス属性によって完全にオーバーライド可能です。 cdefメソッドと比較して、呼び出しのオーバーヘッドを少し追加します。

クラス定義を他のモジュールから見えるようにし、その結果、それらを実装しているモジュールの外で効率的なCレベルの使用と継承を可能にするために、我々はそれらを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 *

これを使用して、統合の例を変更できます:

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

これは、前のコードとほぼ同じ速度ですが、統合する関数を変更できるためはるかに柔軟です。 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

これは約20倍遅くなりますが、それでもオリジナルのPythonのみの統合コードより約10倍速くなります。

evaluate の新しい実装についてのメモ:

  • ここでの高速メソッドディスパッチは、evaluateFunction で宣言されているからこそ動作するのです。
  • 同様に、もし引数 f が型付けされておらず、Python オブジェクトとしてのみ渡されていたら、より遅い Python ディスパッチが使われたことでしょう。 Python では、evaluateメソッドを検索したときに AttributeError となるはずですが、Cython では代わりに NoneFunction であるかのように(互換性のない)内部構造にアクセスしようとして、クラッシュやデータ破壊につながります。

このチェックをオンにするコンパイラ指令 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クラスの属性は通常のクラスの属性とは異なる振る舞いをします。

  • すべての属性はコンパイル時にあらかじめ宣言されなければならない
  • 属性はデフォルトでは Cython からしかアクセスできない (typed access)
  • 動的属性を 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

コメントする