拡張型の定義 (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)

その一方で、 Cython では、Python でいうところの「組み込み型」に基づい たもう一つのクラス: 拡張型 (extension type) をサポートしています。こ のクラスは、クラス宣言のときに使うキーワードから「cdef クラス」ともい います。拡張型は Python のクラスに比べてやや使い勝手に制限がありますが、 おおむねメモリ効率がよく、標準的な Python のクラスよりも動作が高速です。 拡張型の最も大きな違いは、データフィールドやメソッドの保存に、Pythonの 辞書ではなく C の構造体を使うために、任意の C のデータ型を Python ラッ パの介在なくフィールドに保存でき、フィールドやメソッドにアクセスする際 も、Python の辞書を引くことなく C のレベルで直接アクセスできるところで す。

cdef クラスからは通常の Python のクラスを導出できますが、その逆はでき ません。 Cython は C 構造体のレイアウトを決めるために、継承の階層構造 を完全に把握する必要があり、それがゆえに単純継承しかできません。 一方、通常の Python クラスは、 Cython コード上でも Python コード上でも、 Python クラスか拡張型かに関わらずいくつでも継承の親にできます。

さて、これまで挙げてきた例では、予めハードコードされた関数しか積分でき ないので、ちょっと不便でした。そこを手当するために、 cdef クラスを使っ て、浮動小数点を返す関数を表してみます:

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

すでに解説したように、 cpdef を使えば、 Cython から使える高速版メソッ ドと、低速だけれど Python からも使えるメソッドの二つのバージョンを作れ ます。次に:

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

このクラスを使えば、積分の例は以下のように書き換えられます:

def 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 * dx

print(integrate(SinOfSquareFunction(), 0, 1, 10000))

このコードは、以前とほぼ同じくらい高速に動作しますが、積分対象の関数を 自由に変更できるようになった分、柔軟性がぐっと増しました。この関数には、 Python 側で定義した新たな関数も渡せます:

>>> 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 倍ほど高速です。このことは、 ループ全体を Python から Cython のコードに移すことで、いかに簡単に高速 化の恩恵を受けられるかを示しています。

新しい evaluate の実装には、以下のような注意点があります:

  • メソッドの高速ディスパッチが作動するのは、 Function で定義した evaluate に対してだけです。 SinOfSquareFunctionevaluate を定義してしまうと、コードは動作しますが、 Cython はよ り低速な Python のメソッドディスパッチ機構を使ってしまいます。
  • 同様に、引数 f が静的型付けされておらず、 Python オブジェクトと して渡されてしまうと、より低速な Python のディスパッチを使います。
  • 引数が型づけされているので、値が None でないかチェックせねばな りません。 Python では、 evaluate メソッドのルックアップに失敗 した場合には AttributeError になりますが、 Cython では None オブジェクトに対して、あたかも Function オブジェクトであるかの ように、データ構造中 (の存在しない場所) にアクセスしようとして、ク ラッシュしたり、データを破損したりしてしまいます。

Cython には、処理速度の低下と引き換えにチェックを行う コンパイラディレクティブ, nonecheck があります。 nonecheck をコード中で動的にオン・オフする例を以下に示します:

#cython: nonecheck=True
#        ^^^ nonecheck をグローバルでオンにする

import cython

# nonecheck をこの関数ローカルでオフにする
@cython.nonecheck(False)
def func():
    cdef MyClass obj = None
    try:
        # nonecheck をこのブロックだけオンにする
        with cython.nonecheck(True):
            print obj.myfunc() # 例外を送出する
    except AttributeError:
        pass
    print obj.myfunc() # クラッシュするはず。

cdef クラスのアトリビュートは、通常のクラスのアトリビュートとは異なる 振る舞いをします:

  • アトリビュートは全てコンパイル時にあらかじめ定義しなければなりませ ん
  • アトリビュートは通常、 Cython からのみ (型付きで) アクセスできます。
  • プロパティを使えば、Python 側に動的なアトリビュートを公開できます。
cdef class WaveFunction(Function):
    # Python 側からは見えない:
    cdef double offset
    # Python 側から見える:
    cdef public double freq
    # Python 側から見える:
    property period:
        def __get__(self):
        return 1.0 / self. freq
        def __set__(self, value):
        self. freq = 1.0 / value
    <...>

Previous topic

C ライブラリを使う

Next topic

pxd ファイル

This Page