Cython モジュール間で宣言を共有する

この節では、ある Cython モジュールに入っている C の宣言や、関数、拡張 型を、別の Cython モジュールで使えるようにする新しい機能について解説し ます。この機能は、 Python の import 機構に似せてモデル化されており、 いわばコンパイル版の import と考えられます。

定義ファイルと実装ファイル

Cython のモジュールは、二つの部分に分割できます。一つは .pxd サフィッ クスのついた定義ファイルで、ここには他の Cython モジュールでも利用でき る C の宣言 が入ります。もう一つは .pyx サフィックスのついた実装ファ イルで、前者のファイルに含める以外のもの全てを入れます。あるモジュール で他のモジュールの定義ファイルで宣言しているものを使いたい場合には、 cimport 文で import します。

extern 宣言だけの入った .pxd ファイルには、対応する .pyx や Python モジュールは必要ありません。この種のファイルは、よく使う宣言、 例えば external library の関数宣言など、複数 のモジュールで使うものを入れておくのに適しています。

定義ファイルの内容

定義ファイルに入るものは、以下の4種類です:

  • C の型定義
  • extern の C 関数や変数宣言
  • モジュールで定義した C 関数の宣言
  • 拡張型の定義部分 (後の節を参照してください)

定義ファイルには、 extern でない C の変数宣言を入れられません。 また、C や Python 関数の実装、Python のクラス定義、その他実行文なども 入れられません。定義ファイルは、 cdef アトリビュートやメソッ ドにアクセスしたい場合、モジュールで定義している cdef クラ スを継承したいときなどに必要です。

Note

定義ファイルの中では、他の Cython モジュールから使えるようにする目 的で、宣言を public にする必要はありません (してはなりません)。そ もそも、定義ファイルの存在自体がその役割を果たしているからです。 public 宣言は、外部の C のコードから何かを使えるようにしたい時にだ け必要です。

実装ファイルの内容

実装ファイルには任意の Cython の実行文を書けます。ただし、実装ファイル に対応する定義ファイル中で、拡張型の定義を行なっている場合、その実装に は制約が生じます (後の節を参照してください)。 モジュールを別のモジュールから cimport しないのなら、実装ファ イルだけでかまいません。

cimport 文

cimport は、定義ファイルや実装ファイルの中で、他の定義ファ イル中で宣言した名前にアクセスするときに使います。 cimport の構文は、通常の Python の import 文と全 く同じです:

cimport module [, module...]

from module cimport name [as name] [, name [as name] ...]

例を挙げて説明していきます。一つめのファイルは、C のデータ型をエクスポー トしている定義ファイルです。もう一つのファイルは、一つめのファイルを import して使っています。

dishes.pxd:

cdef enum otherstuff:
    sausage, eggs, lettuce

cdef struct spamdish:
    int oz_of_spam
    otherstuff filler

restaurant.pyx:

cimport dishes
from dishes cimport spamdish

cdef void prepare(spamdish *d):
    d.oz_of_spam = 42
    d.filler = dishes.sausage

def serve():
    cdef spamdish d
    prepare(&d)
    print "%d oz spam, filler no. %d" % (d.oz_of_spam, d.filler)

cimport 文は C のデータ型、 C の関数や変数、拡張型の import にだけ使えるということが肝心です。 cimport は Python のオブ ジェクトには使えず、 (ある例外を除いて) 実行時に Python の import を行 うこともありません。 cimport したモジュール上の Python の名 前を参照したいなら、別に import 文を使って include してやる必要があり ます。

例外は、 cimport を使って拡張型を import する場合です。この とき、型オブジェクトは実行時に import され、 import に使った名前で使え るようになります。 cimport を使った拡張型の import について は、後で詳しく解説します。

.pxd ファイルを変更した場合、そのファイルを cimport し ているモジュールは、全てコンパイルし直す必要があります。

定義ファイルのサーチパス

modulename という名前のモジュールを cimport するとき、 Cython コンパイラは、 modulename.pxd という名前のファイルを include ファイルのサーチパスから探します。サーチパスはコマンドラインオ プション -I で指定します。

また、 modulename.pyx をコンパイルするときには、まず同じディレ クトリ下に modulename.pxd がないか探し、もし見つかれば、 .pyx ファイルよりも前に処理します。

cimport を使って名前の衝突を解決する

cimport メカニズムを使えば、外部の C 関数と Python の関数を 同じ名前でラップする際の問題を、クリーンかつシンプルに解決できます。 仮想的なモジュールの .pxd ファイルを作成して、その中に extern C 宣 言を入れておき、それを cimport するのです。そうすれば、C の 関数を、モジュールの名前つきで指定して参照できます。例を示しましょう:

c_lunch.pxd

cdef extern from "lunch.h":
    void eject_tomato(float)

lunch.pyx

cimport c_lunch

def eject_tomato(float speed):
    c_lunch.eject_tomato(speed)

c_lunch.pxd ファイルには、 extern C の定義しかないので、 c_lunch.pyx を容易する必要はありません。実行時に c_lunch モジュールは実在しませんが、何の問題もありません。 c_lunch.pxd が、コンパイル時に名前空間を追加する役割を終えているからです。

C の関数を共有する

モジュールのトップレベルで定義した C の関数は、 .pxd ファイルでヘッ ダを宣言しておけば、 cimport で使えるようになります。例えば:

volume.pxd:

cdef float cube(float)

spammery.pyx:

from volume cimport cube

def menu(description, size):
    print description, ":", cube(size), \
        "cubic metres of spam"

menu("Entree", 1)
menu("Main course", 3)
menu("Dessert", 2)

volume.pyx:

cdef float cube(float x):
    return x * x * x

Note

この方法を使ってモジュールで C 関数をエクスポートすると、モジュー ル辞書に、関数の名前を持ったオブジェクトが出現します。しかし、この オブジェクトは Python からは使えず、Cythonからも通常の import では 使えません。使うには cimport せねばなりません。

拡張型を共有する

拡張型の定義を、定義ファイルと実装ファイルに分割しておくと、 cimport を経由して他のモジュールから使えるようになります。

拡張型の定義部分では、C のアトリビュートとメソッドだけを宣言できます。 Python メソッドは宣言できません。また、拡張型の全ての C アトリビュート と C メソッドを宣言しておかねばなりません。

実装部分では、宣言部分で宣言した全ての C のメソッドを実装せねばなりま せん。また、C アトリビュートを追加してもなりません。実装部分では、 Python メソッドを定義できます。

あるモジュールで拡張型の定義とエクスポートを行い、別のモジュールでそれ を使っている例を、以下に示します:

# Shrubbing.pxd
cdef class Shrubbery:
    cdef int width
    cdef int length

# Shrubbing.pyx
cdef class Shrubbery:
    def __cinit__(self, int w, int l):
        self.width = w
        self.length = l

def standard_shrubbery():
    return Shrubbery(3, 7)


# Landscaping.pyx
cimport Shrubbing
import Shrubbing

cdef Shrubbing.Shrubbery sh
sh = Shrubbing.standard_shrubbery()
print "Shrubbery size is %d x %d" % (sh.width, sh.length)

このコード例では、以下の点に注意が必要です:

  • cdef class 宣言が、 Shrubbing.pxdShrubbing.pyx のどちらにもあります。 Shrubbing モジュールを コンパイルすると、二つの宣言は一つにまとめられます。
  • Landscaping.pyx の中で Shrubbing の宣言を cimport すると、 Shrubbery 型を Shrubbing.Shrubbery で参照できるよ うになります。しかし、これだけでは、実行時に Shrubbing の名前を Landscaping のモジュール名前空間に結び付けられていません。 Shrubbing.standard_shrubbery() にアクセスしたければ、 import Shrubbing も実行する必要があります。