Cython 言語の基礎

Cython のファイルタイプ

Cython には 3 つのファイルタイプがあります:

  • 実装ファイルには拡張子 .pyx をつけます。
  • 定義ファイルには拡張子 .pxd をつけます。
  • includeファイルには拡張子 .pxi をつけます。

実装ファイル

このファイルに書けること

下記の例外を除き、基本的に Cython に関すること全て

このファイルに書けないこと

拡張型 の定義、とりわけ他ですでに定義されている拡張型については制 約があります。(詳しくは後述)

定義ファイル

このファイルに書けること

  • C のデータ型宣言
  • extern C の関数宣言や変数宣言
  • モジュール実装の宣言
  • 拡張型 の定義部分
  • 外部ライブラリ の関数宣言など

このファイルに書けないこと

  • extern C でない変数宣言
  • C や Python の関数の実装部分
  • Python のクラス定義
  • Python の実行文
  • 他の Cython モジュールからアクセスできるように public にした宣言 全て (他のモジュールから自動的にアクセス可能なので、定義ファイルでの 宣言は不要です。 public 宣言は、 外部のCのコード からアクセ スできるようにする場合にのみ必要です)。

その他

cimport
  • 他の定義ファイルや実装ファイルから定義ファイルにアクセスするには cimport 文を Python の import 文のようにして使います。
  • .pyx から同名の .pxdcimport する必要はありません。 Cython があらかじめ同じ名前空間に配置するからです。
  • cimport が定義ファイルを探せるように、 Cython コンパイラコマンド-I オプションでファイルパスを追加できます。
compilation order
  • .pyx ファイルをコンパイルする際、 Cython はまず対応する .pxd ファイルを探し、見つかればそれを先に処理します。

include ファイル

このファイルに書けること

Cython コードは何でも書けます。 include した場所に、そのまま埋め込まれ るからです。

使い方

  • include "spamstuff.pxi のようにして、 .pxi ファイルを include 文で取り込みます。
  • include 文は Cython ファイルのどこにでも、どのインデントレベルで も書けます。
  • .pxi ファイル中のコードはインデントレベル「ゼロ」で書く必要があ ります。
  • include するコードの中にも、別の include 文を書けます。

データ型の宣言

動的言語としての性質から、 Python では、クラスやオブジェクトをクラス階 層中のどこに位置づけるか、よりも、メソッドやアトリビュートをどうするか、 で考えるプログラミングスタイルをとりやすくなっています。

そため、 Python の文法はとてもゆるく、快適かつ迅速に開発を行えます。た だし、その代償として、データ型の管理という「お役所仕事」はインタプリタ に課されています。実行時、インタプリタは名前空間の検索やアトリビュート の取得、引数やキーワード引数のタプルの解析など、様々な仕事を行います。 この実行時の「レイトバインディング」こそ、 C++ のような「アーリーバイ ンディング」型言語に対して Python が低速な主要因です。

一方、 Cython では、アーリーバインディングのプログラミングテクニックを 使って、大幅に速度を向上させられます。

Note

型付けは必須ではない

パラメタや変数の静的型づけはコードの高速化を容易にしますが、必須の 条件ではありません。最適化は必要な場所と時に限りましょう。

cdef 文

cdef 文は、 C レベルの宣言に使います:

変数:
cdef int i, j, k
cdef float f, g[42], *h
構造体:
cdef struct Grail:
    int age
    float volume
共用体:
cdef union Food:
    char *spam
    float *eggs
enum:
cdef enum CheeseType:
    cheddar, edam,
    camembert

cdef enum CheeseState:
    hard = 1
    soft = 2
    runny = 3
関数:
cdef int eggs(unsigned long l, float f):
    ...
拡張型:
cdef class Spam:
    ...

Note

定数

定数は無名 enum で定義できます:

cdef enum:
    tons_of_spam = 3

cdef 宣言のグループ化

cdef 宣言は、一つのグループにまとめられます:

cdef:
    struct Spam:
        int tons

    int i
    float f
    Spam *p

    void f(Spam *s):
    print s.tons, "Tons of spam"

Note

ctypedef 文

型の名前付け用に ctypedef 文があります:

ctypedef unsigned long ULong

ctypedef int *IntPtr

パラメタ

  • C と Python の 関数 型について、C データ型のパラメタを取るよう宣 言できます。

  • 通常の C の宣言構文を使います:

    def spam(int i, char *s):
        ...
    
        cdef int eggs(unsigned long l, float f):
            ...
    
  • パラメタを Python 側で宣言した関数に渡すと、パラメタは指定の C の型 に 変換 されます (数値型と文字列型について適用されます)。

  • パラメタや戻り値型の指定を省略すると、 Python のオブジェクトとみなし ます。以下の関数は、 Python のオブジェクトを引数に取り、Python のオ ブジェクトを返します:

    cdef spamobjs(x, y):
        ...
    

    Note

    ここは C と違う部分です。 C のデフォルトの変数型は int です。

  • Python オブジェクト型には、標準の Python の C-API の規則に従ったリファ レンスカウンティングが行われます。

  • パラメタには借用参照を使います。
  • 新たな参照を戻り値として返します。

Todo

link or label here the one ref count caveat for numpy.

  • Python のオブジェクトを明に宣言したい場合には object を使います。
  • コードの読みやすさを確保するため、普段から object を明示的に使 うよう勧めます。

  • 明示的に宣言しないと、パラメタが他の型とみなされてしまうような場合 に便利です:

    cdef foo(object int):
        ...
    
  • 戻り値型にオブジェクトを指定する場合:

    cdef object foo(object int):
        ...
    

Todo

Do a see also here ..??

オプションの引数

  • cdefcpdef 関数で使えます。
  • .pyx.pxd ファイルかで、宣言方法に違いがあります。
  • .pyx ファイル中では、Python と同じようなシグネチャを使います:

    cdef class A:
        cdef foo(self):
            print "A"
    cdef class B(A)
        cdef foo(self, x=None)
            print "B", x
    cdef class C(B):
        cpdef foo(self, x=True, int k=3)
            print "C", x, k
    
  • .pxd ファイル中では、以下の例の cdef foo(x=*) のようにシグ ネチャが変わります:

    cdef class A:
        cdef foo(self)
    cdef class B(A)
        cdef foo(self, x=*)
    cdef class C(B):
        cpdef foo(self, x=*, int k=*)
    
  • サブクラスで引数の数を増やしてもかまいませんが、引数の型と順番は同 じにせねばなりません。
  • デフォルト値を持たないオプション引数をオーバライドすると、多少のパフォ ーマンスペナルティが生じます。

キーワードのみの引数

  • Python 3 から、 def 型の関数には、 "*" パラメタの後かつ "**" よりも前にキーワードのみの引数 (keyword-only argument) を持 たせられます:

    def f(a, b, *args, c, d = 42, e, **kwds):
        ...
    
  • 上の c, d, e は位置固定引数にできず、キーワード引数とし て渡さねばなりません。
  • さらに、 ce はデフォルト値を持たないので、必須の引数で す。
  • "*" の後のパラメタ名 (上では args)を省略すると、関数は位置固 定引数を追加で取れなくなります:

    def g(a, b, *, c, d):
        ...
    
  • 上の例のシグネチャでは、位置固定引数を二つと、必須のキーワード引数 を二つ取ります。

自動型変換

  • ほとんどの場合、 Python オブジェクトと C の値との間の相互変換は、 C の基本数値型や文字型との間で行われます。

  • 以下の表は、 sizeof(int) == sizeof(long) のときの型変換について まとめています:

    C の型

    変換元の Python 型

    変換先 Python 型

    [unsigned] char

    int, long

    int

    [unsigned] short

    int, long

    unsigned int

    int, long

    long

    unsigned long

    [unsigned] long long

    float, double, long double

    int, long, float

    float

    char *

    str/bytes

    str/bytes [1]

    struct

     

    dict

Note

C のコンテキストにおける Python 文字列の扱い

  • char* で受けることを想定している C のコンテキストに Python の文字列を渡した場合、文字列データは Python の文字列オブジェクト が存在する間だけ有効です。

  • C の文字列が必要な間、 Python 文字列への参照を保持しなければなり ません。

  • Python 文字列への参照を保証できない場合は、 C 文字列のコピーを生 成してください。

  • 以下のようなコードでは、 Cython は Obtaining char* from a temporary Python value のようなメッセー ジを出して、コンパイルを止めてしまいます:

    cdef char *s
    s = pystring1 + pystring2
    
  • これは、 Python での文字列の結合操作で一時的な値としての Python 文字列ができるためです。文の実行が終わると、変数は decref され、 Python 文字列はすぐにデアロケートされてしまいます。そのため、 左辺値の ``s`` がぶら下がりポインタになってしまうのです。

  • 解決するには、文字列の結合結果を Python の変数に代入しておいてか ら、その値の char* を得ます:

    cdef char *s
    p = pystring1 + pystring2
    s = p
    

Note

文字列の扱いについては、 Cython のエラーメッセージに頼らず注意してください。エラーメッセージがどのような状況でも的確に出るとは限りません。

型キャスト

  • "<"">" を使った構文で型キャストを行います。

Note

この構文は C の型変換の構文とは異なります。

cdef char *p, float *q
p = <char*>q
  • <type>x の型のいずれかが Python のオブジェクトであれば、 Cython は型強制を試みます。

Note

変換がなくても Cython はキャストを止めませんが、警告を出し ます。

  • アドレスが必要なら、まず void* にキャストしてください。

型チェック

  • <MyExtensionType>x のようなキャストは、 x を型チェックせずに MyExtensionType にキャストします。
  • 型チェック付きのキャストを行いたければ、 <MyExtensionType?>x の 構文を使います。型チェックを行った場合、 "x"MyExtensionType (のサブクラス) でなければエラーを送出します。
  • isinstance() の第二引数に拡張型を指定すると、自動型チェックを行 います。

実行文と式

  • 制御構文や式は、ほぼ Python の文法に従います。
  • Python オブジェクトに関する操作のセマンティクスは、特に注意のない限 り Python と同じです。
  • C の値に対する Python の演算操作も可能で、自明なセマンティクスです。
  • Python と C の値を混在させた式では、自動的に値の 変換 が起こりま す。
  • Python の演算では、エラーチェックが自動的に行われ、適切に処理されま す。

Cython と C の違い

  • 最もはっきりした違いは、 Python と直接的に等価でない C のコンストラ クトの存在です。
  • 整数リテラルは C の定数として扱います。
  • 整数値は、 C コンパイラにとって適切なサイズに切り詰められます。

  • C の整数型に収まらない値は、以下のように Python オブジェクトにキャ ストしてください:

    <object>10000000000000000000
    
  • "L", "LL", "U" といったサフィックスは、 C でのサフィッ クスと同じ意味です。

  • Cython には -> 演算子がありません。 p->x の代わりに p.x を使って下さい。
  • Cython には * 演算子がありません。 *p の代わりに p[0] を 使ってください。
  • & は利用でき、 C と同じセマンティクスです。
  • NULL は C のヌルポインタを表します。 0 を使ってはなりません。 NULL は Cython の予約語です。
  • 型キャスト<type>value のように書きます。

スコープ規則

  • Cython では、変数スコープ (local, module, built-in) を静的に決定しま す。
  • Python と同様、明示的に宣言していない変数への代入を行うと、その変数 を暗黙のうちに代入を実行したスコープで宣言したものとみなします。

Note

  • ある変数を代入する前に参照すると、モジュールレベルのスコープは Python のローカルスコープと同じように振る舞います。以下のような トリックは、 Cython では使えません:

    try:
        x = True
    except NameError:
        True = 1
    
  • なぜなら、try 節での代入操作によって、True は常にモジュールレベ ルのスコープ上でルックアップされるようになってしまうからです。同 様のことをしたければ、下記のように書くとよいでしょう:

    import __builtin__
    try:
        True = __builtin__.True
    except AttributeError:
        True = 1
    

ビルトイン定数

以下の Python ビルトイン定数を予め定義しています:

  • None
  • True
  • False

演算子の優先順

  • Cython では C ではなくPython の優先順位を使います。

整数の for ループ

  • インデクス変数を cdef で宣言すると、 `range() を C レベルで 最適化します:

    cdef i
    for i in range(n):
        ...
    
  • C のアレイにわたるイテレーションも可能です:

    cdef double x
    cdef double* data
    for x in data[:10]:
        ...
    
  • リストやタプルのような組み込み型の多くに対して、イテレーションが最適 化されています。

  • C スタイルの for-from 構文もあります。

  • ターゲットの式は変数名でなければなりません。

  • 下界値と上界値の表現に使う変数名と、ターゲットの変数名は同じにせね ばなりません。

    for i from 0 <= i < n:

    ...

  • ステップサイズを使う場合は以下のように書きます:

    for i from 0 <= i < n by s:
        ...
    
  • イテレーション方向を逆にするには、比較演算子を逆向きにします:

    for i from n > i >= 0:
        ...
    
  • breakcontinue を使えます。
  • else 節を使えます。

関数とメソッド

  • Cython には、3 種類の関数宣言があります。
  • Cython モジュールの外の Python インタプリタコード から呼び出せるの は、「Python 形式の」関数のみです。

Python から呼び出せる関数

  • def 文で定義した関数です。
  • Python オブジェクトを引数に取ります。
  • Python オブジェクトを返します。
  • 注意事項は パラメタ の節を参照してください。

C から呼び出せる関数

  • cdef 文で定義した関数です。
  • Python オブジェクトか C の値を引数に取ります。
  • Python オブジェクトか C の値を返します。

Python と C の両方から呼び出せる関数

  • cpdef 文で定義した関数です。
  • Cython のちょっとした工夫によって、C と Python のどちらからでも呼び 出せます。
  • 他の Cython コードから呼び出す場合には、より高速な C の呼び出し既約 を使います。

オーバライド

cpdef 関数で cdef 関数をオーバライドできます:

cdef class A:
    cdef foo(self):
        print "A"
cdef class B(A)
    cdef foo(self, x=None)
        print "B", x
cdef class C(B):
    cpdef foo(self, x=True, int k=3)
        print "C", x, k

関数ポインタ

  • struct 中で宣言した関数は自動的に関数ポインタに変換されます。

Python のビルトイン関数

以下のビルトイン関数を提供しています:

Todo

incomplete

関数と引数 戻り値 等価な Python/C API
abs(obj) object PyNumber_Absolute
bool(obj) object Py_True, Py_False
chr(obj) object char
delattr(obj, name) int PyObject_DelAttr
dir(obj) getattr(obj, name) (Note 1) getattr3(obj, name, default) object PyObject_Dir
hasattr(obj, name) int PyObject_HasAttr
hash(obj) int PyObject_Hash
intern(obj) object PyObject_InternFromString
isinstance(obj, type) int PyObject_IsInstance
issubclass(obj, type) int PyObject_IsSubclass
iter(obj) object PyObject_GetIter
len(obj) Py_ssize_t PyObject_Length
pow(x, y, z) (Note 2) object PyNumber_Power
reload(obj) object PyImport_ReloadModule
repr(obj) object PyObject_Repr
setattr(obj, name) void PyObject_SetAttr

エラーと例外処理

  • Python オブジェクトを返さない素の cdef 宣言した関数は、呼び出し 側に Python 例外を報告する手段がありません。そのため、例外メッセージ だけを出力して、例外を無視します。
  • 例外を呼び出し側に伝播するには、例外値 (exception value) を宣言せね ばなりません。
  • C でコンパイルするプログラムで例外を宣言するには、三つの方法がありま す。
  • その1:

    cdef int spam() except -1:
        ...
    
  • spam の中でエラーが発生すると、関数は即座に -1 を返し、それに よって呼び出し側に例外を伝播します。
  • 例外値を宣言している関数は、例外値を return で明示的に返してはなり ません。
  • その2:

    cdef int spam() except? -1:
        ...
    
  • -1 を return で返してもよく、 -1 をすなわちエラーとはみな しません。
  • "?" は、 Cython に -1 はエラーの 可能性がある と教えるだ けです。
  • そこで、関数が -1 を返すたびに、 Cython は PyErr_Occurred を呼んで、実際にエラーが生じていないか調べます。
  • その3:

    cdef int spam() except *
    
  • 関数を呼び出すたびに、 毎回 PyErr_Occurred を呼びます。

    Note

    void を返す

    戻り値型が void の関数は、このバージョンを使わねばなりませ ん。

  • 例外値を宣言できるのは、関数の戻り値が以下の場合だけです:
  • 整数型
  • enum
  • float型
  • ポインタ型
  • 定数式

Note

Note

関数ポインタ

  • 関数ポインタの型宣言でも、例外値の型指定を行わねばなりません。

  • パラメタや変数に代入するときの型宣言を以下に示します:

    int (*grail)(int, char *) except -1
    

Note

Python オブジェクト

  • 例外値の宣言は 不要 です。
  • 戻り値の宣言を省略すると、暗黙のうちに Python オブジェクトを 返すということを思い出して下さい。
  • そのような関数は、 NULL を返すことで、例外の伝播を行なっ ています。

Note

C++

非 Cython 関数の戻り値をチェックする

  • 例外値として指定した値を明に返して、例外状況を発生させようとしてはな りません:

    cdef extern FILE *fopen(char *filename, char *mode) except NULL # WRONG!
    
  • その方法では except 節は働きません。
  • 例外値の宣言は、 Cython の関数や、 Python/C APIルーチンの中で送出され た Python の例外を伝播するためのものです。
  • Python 対応でない関数から例外を送出させるには、以下の例のように、自分 で戻り値をチェックして例外を送出する必要があります:

    cdef FILE *p
    p = fopen("spam.txt", "r")
    if p == NULL:
        raise SpamError("Couldn't open the spam file")
    

条件付きコンパイル

  • 条件付きコンパイルのサブセクションに指定する式は、コンパイル時に解釈 される式として有効なものでなければなりません。
  • 任意の Python 値を評価できます。
  • 式の 真偽値 は Python と同じ方法で判定されます。

コンパイル時の定義

  • コンパイル時の定義は、 DEF 文で定義できます:

    DEF FavouriteFood = "spam"
    DEF ArraySize = 42
    DEF OtherArraySize = 2 * ArraySize + 17
    
  • DEF の右側値は、コンパイル時に解釈される式として有効なものでなけ ればなりません。式は、リテラル値と、他の DEF 文で予め定義した名 前で構成します。

  • Python の構文を自由に使って式を組めます。

  • Cython は以下のコンパイル時変数名を予め定義しています。これらの値は、 それぞれ os.uname() の返す値に対応しています。

    • UNAME_SYSNAME
    • UNAME_NODENAME
    • UNAME_RELEASE
    • UNAME_VERSION
    • UNAME_MACHINE
  • DEF を使って定義した名前は、識別子 (identifier) を書けるところな らどこにでも記述できます。

  • Cython はコンパイル前に名前をリテラル値に置き換えます。そのため、コ ンパイル時の式は、最終的には Python の int, long, float, str のいずれかに評価されなければなりません:

    cdef int a1[ArraySize]
    cdef int a2[OtherArraySize]
    print "I like", FavouriteFood
    

条件付きコンパイル文

  • C のプリプロセッサディレクティブと似たセマンティクスです。
  • 以下の文を使って、コードセクションをコンパイル対象に含めたり、除外し たりできます。
  • IF
  • ELIF
  • ELSE
IF UNAME_SYSNAME == "Windows":
    include "icky_definitions.pxi"
ELIF UNAME_SYSNAME == "Darwin":
    include "nice_definitions.pxi"
ELIF UNAME_SYSNAME == "Linux":
    include "penguin_definitions.pxi"
ELSE:
    include "other_definitions.pxi"
  • ELIF および ELSE 節は省略可能です。
  • IF 文は、通常の実行文や宣言を書けるところなら、どこにでも書けま す。
  • あるコンテキスト下で書ける文や宣言なら、 DEF 文や他の IF 文 も含めて何でも書けます。
[1]変換は Python 2.x では str に対して、 Python 3.x では bytes に 対して行われます。