拡張型

  • 通常の Python クラスの他に、拡張型クラス (extension type class) を定 義できます。
  • 拡張型は:
    • Python からは組み込み型 (built-in type) のように見えます。
    • 任意の C データ構造をラップするのに使えて、Python からは Python ライク なインタフェースでアクセスできます。
    • 拡張型のアトリビュートやメソッドは、Python と Cython のどちらから も呼び出せます。
    • cdef class 文で定義します。
cdef class Shrubbery:

    cdef int width, height

    def __init__(self, w, h):
        self.width = w
        self.height = h

    def describe(self):
        print "This shrubbery is", self.width, \
            "by", self.height, "cubits."

アトリビュート

  • オブジェクトの C 構造体に直接保存されます。

  • どのようなアトリビュートをもたせるかは、コンパイル時に決定していなけ ればなりません。

    • 通常の Python のオブジェクトのアトリビュートと違って、拡張型には、 実行時にアトリビュートを追加できません。
    • Python側で拡張型のサブクラスを作れば、実行時にアトリビュートを追加 できます。
  • 拡張型のアトリビュートには、二つの方法でアクセスできます。

    • 一つはPython のアトリビュートルックアップで、Python ではこの方法し か使えません。
    • もう一つは、Cython のコードから C の構造体に直接アクセスするもので、 Cython からは上のルックアップとこの方法の両方を使えます。
  • アトリビュートには、デフォルトの状態では直接アクセスしかできず、 Python のコードからはアクセスできません。

  • アトリビュートを Python からアクセスするには、アトリビュートを public または readonly で宣言せねばなりません:

    cdef class Shrubbery:
        cdef public int width, height
        cdef readonly float depth
    
    • この例では、 widthheight アトリビュートは Python のコー ドから読み書きできます。
    • depth アトリビュートは Python からは読み出しのみ可能で、変更 はできません。

Note

Python からアクセス可能なアトリビュートにできるのは、int, float, string といった単純な C の型だけです。その他には、Python の値を アトリビュートとして公開できます。

Note

publicreadonly といったオプションは Python からのア クセスにのみ影響を及ぼします。拡張型のアトリビュートは全て、 C レベルで読み書きアクセスできます。

メソッド

  • Python のメソッド定義と同様、拡張型でも、 self を使います。
  • 関数とメソッド も参照してください。

プロパティ

  • 以下のような特殊な構文で宣言します:

    cdef class Spam:
    
        property cheese:
    
            "プロパティの docstring はここに書く"
    
            def __get__(self):
                # プロパティの値を読むときに呼び出される
                ...
    
            def __set__(self, value):
                # プロパティの値を変更するときに呼び出される
                ...
    
            def __del__(self):
                # プロパティの値を削除するときに呼び出される
    
  • __get__(), __set__(), __del__() メソッドは、それぞれ省略 できます。省略すると、各メソッドに対応する操作を行った時に例外を送出 します。

  • プロパティを定義したクラスの例を以下に示します。

    • プロパティに値を入れる ("__set__") と、その値をリストに追加し ます。

    • 値を参照する ("__get__") と、リスト全体を返します。

    • 値を削除する ("__del__") と、リストを空にします。:

      # cheesy.pyx
      cdef class CheeseShop:
      
          cdef object cheeses
      
          def __cinit__(self):
              self.cheeses = []
      
          property cheese:
      
              def __get__(self):
                  return "We don't have: %s" % self.cheeses
      
              def __set__(self, value):
                  self.cheeses.append(value)
      
              def __del__(self):
                  del self.cheeses[:]
      
      # Test input
      from cheesy import CheeseShop
      
      shop = CheeseShop()
      print shop.cheese
      
      shop.cheese = "camembert"
      print shop.cheese
      
      shop.cheese = "cheddar"
      print shop.cheese
      
      del shop.cheese
      print shop.cheese
      
# Test output
We don't have: []
We don't have: ['camembert']
We don't have: ['camembert', 'cheddar']
We don't have: []

特殊メソッド

Note

  • 特殊メソッドに対する Cython のセマンティクスは、基本的に Python の ものと同じです。
  • いくつか明確に異なるものもあります。
  • Cython の特殊メソッドの中には、 Python 側で対応する特殊メソッドの 存在しないものもあります。

特殊メソッドの宣言

  • 必ず def で宣言します。 cdef では宣言できません。
  • 呼び出し時に特別扱いされるため、 def で宣言してもパフォーマンス 上の影響をうけません。

ドキュメンテーション文字列

  • 特殊メソッド型の docstring はまだサポートしていません。
  • ソースコードに docstring を書くことは可能ですが、その内容を実行時に __doc__ アトリビュートで見ることはできません。これは、 PyTypeObject データ型に制約があるという、 Python ライブラリ側の 問題です。

初期化: __cinit__()__init__()

  • 拡張型のコンストラクタには任意の数の引数を指定できますが、引数はどち らのメソッドにも渡ります。

  • __cinit__() は、 C データ構造のアロケーションをはじめ、オブジェ クトを C レベルで初期化するための処理を書く場所です。このメソッドで 何かする場合には、以下の点に注意してください:

    • メソッドが呼び出された段階では、オブジェクトはまだ Python オブジェ クトとして完全に有効な状態ではないことがあります。
    • 拡張型自身のメソッドを含み、Python オブジェクトとしてメソッド呼び 出しを行うのは危険です。
  • __cinit__() メソッドが呼び出される前に、拡張型は以下のような状態 になっています:

    • オブジェクトのメモリ領域がアロケーションされています。
    • C レベルのアトリビュートは、全て 0 か NULL に初期化されています。
    • Python オブジェクトのアトリビュートは None に初期化されています。 ただし、そのことに依存してはなりません。
    • 初期化メソッドは、必ず一度だけ呼び出されることが保証されています。
  • 何らかの基本型を継承している拡張型の場合:

    • 基本型の __cinit__() メソッドがまず呼び出され、その後に拡張型 の __cinit__() が呼ばれます。
    • 基本型から継承した __cinit__() は、明に呼び出せません。
    • 引数リストを変更して基本型に渡したい時には、 __init__() を経由 せねばなりません。
    • __cinit__() メソッドに "*""**" といった引数を用意 しておくのは賢い方法です。
      • 余分な引数を受け取ったり、無視したりさせられます。
      • Python レベルでサブクラス化を行って __init__() メソッドのシ グネチャを変えたいときに、 __new__()__init__() メソッ ドの両方をオーバライドする必要がなくなります。
    • self 以外の引数を持たないように __cinit__() を宣言すると、 コンストラクタにいくつ引数を渡しても全て無視し、シグネチャの不一 致が起きても文句を言わなくなります。
  • __cinit__() が低水準の初期化をするのに対し、 __init__() は高 水準の初期化に使われ、 Python からも安全にアクセスできます。

    • __init__() が呼び出される段階では、拡張型は Python オブジェク トとして完全に有効な状態になっており、安全に操作できます。
    • このメソッドは複数回呼び出されることもあれば、全く呼び出されないこ ともありえます。そのため、他のメソッドは、 __init__() が呼ばれ ても呼ばれなくても影響を受けないような設計にしましょう。

後始末: __dealloc__()

  • __cinit__() の対極に位置するメソッドです。
  • 明示的にアロケートした C のデータ用のメモリ領域は、 このメソッドで free せねばなりません。
  • このメソッドを使う場合は、以下に注意しましょう:
    • このメソッドを所有している Python オブジェクトは、メソッドが呼び出さ れた時点で、すでに完全なオブジェクトの状態でないことがあります。
    • __dealloc__() 対象のオブジェクトを触るような Python の操作は呼ば ないようにしましょう。
    • __dealloc__() 対象のオブジェクトのメソッドは一切呼び出してはなり ません。
    • __dealloc__() の実装では、単に C のデータ構造のためにアロケートし た領域の開放だけにするのがベストです。
  • 拡張型オブジェクトの Python のアトリビュートの領域は、 __dealloc__() メソッドが処理を戻した後に、 Cython によって開放さ れます。

算術メソッド

Note

ほとんどの算術メソッドの挙動は、 Python のメソッドと異なります。

  • 「逆向き」バージョン、たとえば __radd__() はありません。
  • 最初の被演算子が目的の演算を実行できなければ、ふたつめの被演算子の算 術メソッドが、被演算子の順番を同じにして呼び出されます。
  • メソッドの最初のパラメタが "self" であったり、適切なデータ型であ ると期待してはなりません。
  • 必ず、処理を行う前に、被演算子の両方に対して型を調べて下さい。
  • 扱えないデータ型や、適合しないデータ型の場合には NotImplemented を返します。
  • 上記の振る舞いは...
  • インプレース演算メソッド __ipow__() にも当てはまります。
  • その他の、 __iadd__() のようなインプレース演算メソッドには当 てはまらず、常に self を第一引数として受け取ります。

リッチな比較

Note

各々のリッチ比較演算のための固有の特殊メソッドはありません。

  • 全てのリッチ比較に対して、 __richcmp__() が呼び出されます。

  • __richcmp__() は、どのような演算を実行するかを表す整数の引数をと ります。引数の意味は下記の表の通りです。

    <

    0

    ==

    2

    >

    4

    <=

    1

    !=

    3

    >=

    5

サブクラス化

  • 拡張型は、ビルトイン型や、他の拡張型を継承できます:

    cdef class Parrot:
        ...
    
    cdef class Norwegian(Parrot):
        ...
    
  • 型を継承する場合、基底型の完全な定義を Cython に伝えねばなりません。

  • 基底型がビルトイン型の場合、事前に extern 拡張型として宣言され ていなければなりません。
  • 基底型が他の .pxd ファイル中にある場合は、 extern 拡張型と して宣言されているか、 cimport で import せねばなりません。
  • Cython では、多重継承は行えません。単純継承のみが可能です。
  • Cython の拡張型を Python でサブクラス化することも可能です。
  • その場合、普通の Python のクラス同様、多重継承が可能です。
  • 複数の拡張型に対する多重継承も可能ですが、その場合には、全ての基底 クラスの C データ型のレイアウトに互換性がなければなりません。

前方宣言 (forward declaration)

  • 拡張型は前方宣言 (forward declaration) できます。

  • 二つの拡張型が存在して、お互いを参照している場合に必要です:

    cdef class Shrubbery # forward declaration
    
    cdef class Shrubber:
        cdef Shrubbery work_in_progress
    
    cdef class Shrubbery:
        cdef Shrubber creator
    
  • 基底クラスを持つ拡張型を前方宣言する場合、前方宣言とクラス定義の両方で 基底クラスを指定せねばなりません:

    cdef class A(B)
    
    ...
    
    cdef class A(B):
        # attributes and methods
    

拡張型と None

  • パラメタや C の変数を拡張型で宣言すると、 Cython はその値が拡張型オ ブジェクトの他に None も取りうるとみなします。
  • C のポインタが NULL を値に取れるのに似ています。

Note

  1. None を使う場合には、よく注意してください。
  2. この節をよく読んで下さい。
  • 拡張型に対して Python の操作を行なっている間は、完全な動的型チェック が行われるため、何の問題もありません。

  • 拡張型の C アトリビュートに (上の widen_shrubbery のように) アクセス する場合には、オブジェクトが None でないかどうかを自分で調べねば なりません。効率重視のため、 Cython はチェックを行いません。

  • 拡張型を引数に取る Python の関数を公開するときは、特に注意が必要です:

    def widen_shrubbery(Shrubbery sh, extra_width): # This is
    sh.width = sh.width + extra_width
    
  • モジュールのユーザが shNone を渡すとクラッシュしてしまい ます。

  • 以下のように書くと回避できます:

    def widen_shrubbery(Shrubbery sh, extra_width):
        if sh is None:
            raise TypeError
        sh.width = sh.width + extra_width
    
  • Cython には not None 節を使ったもっと便利な方法があります:

    def widen_shrubbery(Shrubbery sh not None, extra_width):
        sh.width = sh.width + extra_width
    
  • これで、この関数は、引数が正しい型であるかどうかのチェックと同時に、 shnot None でないかも自動的にチェックします。

  • not None 節を使えるのは、 Python の関数 (def で定義し

    たもの) だけです。

  • cdef で定義した関数は自分でチェックせねばなりません。

  • 拡張型のメソッドの self パラメタは、必ず None でないことが保 証されています。

  • 値を None と比較するときに注意せねばならないのは、 x が Python のオブジェクトのとき、 x is Nonex is not None は C のポインタ比較に翻訳されるためにとても効率的であるということです。 一方、 x == Nonex != None、 あるいは単に x のブール 値評価 (if x: ... のような書き方) は、Python の演算を呼び出すた め、かなり低速になります。

弱参照

  • デフォルトの状態では、拡張型に対する弱参照は作成できません。

  • 弱参照を有効にするには、 object 型の C アトリビュート __weakref__ を宣言します:

    cdef class ExplodingAnimal:
        """This animal will self-destruct when it is
        no longer strongly referenced."""
    
        cdef object __weakref__
    

public および extern 拡張型

public

  • 拡張型を public で宣言すると、Cython は .h ファイルを生成し ます。
  • ヘッダファイルの中には、 オブジェクトの構造体型オブジェクトの宣言 が入ります。
  • 外部の C コードからは、拡張型のアトリビュートとしてアクセスできます。

extern

  • extern 拡張型を使うと、 Python のコアシステムで定義されている Python オブジェクトの内部構造にアクセスしたり、 Cython を使わない拡張モジュー ルから拡張型にアクセスしたりできます。

  • ビルトインの複素数オブジェクトの C レベルのメンバを取り出す例を示し ます:

    cdef extern from "complexobject.h":
    
        struct Py_complex:
            double real
            double imag
    
        ctypedef class __builtin__.complex [object PyComplexObject]:
            cdef Py_complex cval
    
    # 上の型を使う関数
    def spam(complex c):
        print "Real:", c.cval.real
        print "Imag:", c.cval.imag
    

Note

いくつか重要な点があります:

  1. この例では ctypedef クラスを使っています。 Python のヘッダファイルで PyComplexObject 構造体を 以下のように宣言しているためです:

    typedef struct {
        ...
    } PyComplexObject;
    
  2. 拡張型の名前の他に、Python の型オブジェクトが入っているモジュー ルも指定しています。これについては、 暗黙の import の節を参照してください。

  3. extern 拡張型を宣言する際は、メソッドを宣言しないでください。 extern 拡張型のメソッドは Python のメソッドなので、宣言しなくて も呼び出せます。また、 structunion と 同様、拡張クラスの宣言が cdef extern from ブロックの中にあ る場合には、アクセスしたい C のメンバだけを宣言してください。

型の名前宣言のための節

Note

extern および public 拡張型だけで使えます。

  • 書き方の例:

    [object object_struct_name, type type_object_name ]
    
  • object_struct_name は、拡張型の C 構造体に割り当てる名前です。

  • type_object_name は拡張型の静的に宣言された type オブジェクトに 割り当てる名前です。

  • object 節と type 節は、どちらの順番でも書けます。

  • 拡張型宣言を cdef extern from ブロックの中で行う場合は、Cython がヘッダファイルの宣言と互換性のあるコードを生成しなければならないた め、 object 節が必須 です。それ以外の場合には、 extern 拡張型で は object 節は不要です。

  • public 拡張型の場合、Cython が外部の C コードと互換性のあるコードを 生成しなければならないので、 object 節と type 節の両方が必要です。

型の名前とコンストラクタの名前

  • Cython モジュール中では、拡張型の名前には二つの異なる用途があります:

    1. 式の中で使うと、拡張型のコンストラクタ (すなわち、型オブジェクト) を保持するモジュールレベルでグローバルな変数を表します。
    2. 変数や引数、戻り値の宣言に使う C の型の名前にも使えます。
  • 書き方の例:

    cdef extern class MyModule.Spam:
        ...
    
  • Spam という名前は、前述の二つの役割を果たします。
  • 型宣言に使えるのは、 Spam だけです。
  • コンストラクタの参照には、他の名前を使えます。
  • 明示的に MyModule を import した場合には...
    • MyModule.Spam()Spam インスタンスを生成できます。
    • MyModule.Spam を型の宣言には使えません。
  • as 節を使うと、 as で指定した名前に両方の役割 が引き継がれます:

    cdef extern class MyModule.Spam as Yummy:
        ...
    
  • Yummy は型の名前であると同時に、コンストラクタでもあります。
  • 別の名前でもコンストラクタとしては使えますが、型の名前として使える のは Yummy だけです。