基本の言語仕様

C の変数と型定義

cdef 文は、ローカルやモジュールレベルの C の変数を宣言する のに使います:

cdef int i, j, k
cdef float f, g[42], *h

また、 struct, union, enum の定義にも 使います:

cdef struct Grail:
    int age
    float volume

cdef union Food:
    char *spam
    float *eggs

cdef enum CheeseType:
    cheddar, edam,
    camembert

cdef enum CheeseState:
    hard = 1
    soft = 2
    runny = 3

構造体、共用体、 enum の宣言スタイル も参照してください。

現状、定数型を定義するための特別な構文はありませんが、 enum 宣言を使えば、ほぼ同じ目的を果たせます:

cdef enum:
    tons_of_spam = 3

Note

struct, union, enum を使えるのは型の定義だけで、参照に は使えません。例えば、 Grail 型を指す変数の宣言は:

cdef Grail *gp

のように書き、t:

cdef struct Grail *gp # 間違い

のようには書けません。

データ型に名前を付ける構文には ctypedef もあります:

ctypedef unsigned long ULong

ctypedef int *IntPtr

複数の C 変数宣言をグループ化する

cdef で始まる変数宣言がたくさんあるときには、以下のように、 cdef をブロックとして書くことでグループ化できます:

cdef:
    struct Spam:
        int tons

    int i
    float f
    Spam *p

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

Python の関数と C の関数

Cython には二つの種類の関数定義があります:

一つは def 文を使った Python の関数定義で、 Python と同様、 Python の オブジェクトをパラメタにとり、 Python のオブジェクトを返します。

もう一つは cdef 文で定義する C の関数です。この関数は Python のオブジェクトまたは C の値をパラメタにとり、戻り値も Python の オブジェクトまたは C の値で返せます。

Cython モジュールの中では、 Python の関数と C の関数は、相互に自由に 呼び出せます。ただし、 Python の関数だけは、モジュールの外側で、インタ プリタ実行される Python のコードとして呼び出されます。そのため、Cython モジュールの外側に「エクスポート」したい関数は、 def を使って Python の関数として定義せねばなりません。

Python と Cython の両方から呼び出せるハイブリッド型の関数 cpdef もあります。 cpdef は、他の Cython コード の中から呼び出された場合には、より高速な C の呼び出し方法を使います。

どちらのタイプの関数も、パラメタを C のデータ型で宣言できます。宣言は、 通常の C の型宣言の構文を使います。例えば:

def spam(int i, char *s):
    ...

cdef int eggs(unsigned long l, float f):
    ...

Python 関数のパラメタを C のデータ型で宣言すると、パラメタは Python オ ブジェクトとして渡され、その後に自動的に C の値に (可能ならば) 変換さ れます。現状、自動型変換が可能なのは数値型、文字列型、構造体 (または、 これらの型の再帰的な複合型) に限られています。その他の型をパラメタで宣 言しようとすると、コンパイル時にエラーを起こします。 文字列をパラメタにする場合、文字列へのポインタを関数呼び出し後も使うつ もりなら、パラメタに渡したオブジェクトへのリファレンスが無くならないよ う注意しましょう。構造体は Python のマップ型のパラメタから取得できます が、このときにも、関数が処理を戻した後に文字列アトリビュートへの参照を 使いたいなら、注意が必要です。

一方、C の関数は、通常の C の関数呼び出しで直接パラメタを渡すので、ど んな型でも指定できます。

二つのメソッドタイプの間の長所と短所は、 アーリーバインディングによる高速化 でも詳しく比較しています。

パラメタや戻り値の Python オブジェクト

パラメタや戻り値の型の指定を省略した場合、Cython はそれらを Python オ ブジェクトとみなします。 (C の流儀とは違っていて、C では int とみなし ます。) 例えば、以下のコードでは、 Python オブジェクトをパラメタにとり、 Python オブジェクトを返す C の関数を定義しています:

cdef spamobjs(x, y):
    ...

オブジェクトの参照カウントは、標準の Python/C API ルールに従って自動的 に行われます (パラメタには借用リファレンスを使い、戻り値には新たな参照 を生成します)。

Python オブジェクトとして明示的に宣言するときには、変数型名 object を使えます。 object は、変数型名と同じ名 前の変数を使いたい場合などに、変数名が誤って宣言として扱われるのを防ぐ のに使えます。例えば、以下の宣言:

cdef ftang(object int):
    ...

では、 int という名前のパラメタを、 Python のオブジェクト型で宣言 しています。 object は、関数の戻り値の型を明示するときにも 使えます:

cdef object ftang(object int):
    ...

C の関数で object 型のパラメタを使う場合は、常に明示的に宣 言した方が、明確になってよいかもしれません。

エラーを伴う戻り値 (error return value)

特別なことをしなければ、 cdef で宣言した関数であって Python オブジェクトを返さないものは、呼び出し側に Python の例外を報告する術が ありません。そのような関数の中で例外が検出されると、警告メッセージが出 力され、例外は無視されます。

Python オブジェクトを返さない C の関数が、呼び出し側に例外を伝播できる ようにしたければ、例外が発生したときの戻り値を宣言する必要があります。 以下にその例を示します:

cdef int spam() except -1:
    ...

この宣言は、 spam の中で例外が生じた時に、戻り値 -1 で即座に処 理を戻します。さらに、 spam への呼び出しが -1 を返した場合、呼び 出し側は例外が発生したとみなして、例外を伝播します。

関数の例外値を宣言する場合は、決してその値を明示的に返してはなりません。 戻り値の値域が全域正当な値であり、エラーの通知だけに使える値を確保でき ない場合には、もう一つの形式で例外値を宣言します:

cdef int spam() except? -1:
    ...

"?" は、 -1 は単にエラーの可能性があるに過ぎないということを表 します。その場合、 Cython は PyErr_Occurred() を呼び出して、例 外オブジェクトの値が返されているかどうかを調べて、本当にエラーかどうか をチェックします。

例外値の宣言には、第三の形式もあります:

cdef int spam() except *:
    ...

この形式で宣言すると、 Cython は spam を呼び出した後に、戻り値に関わら ず、常に PyErr_Occurred() の呼び出しを行います。 void を返す 関数でエラーの伝播を行いたい場合、関数は有意にテストできる戻り値を返さ ないので、この形式を使わねばなりません。それ以外には、この形式の使い道 はあまりないでしょう。

例外を返しうる外部の C++ の関数は、以下のように宣言できます:

cdef int spam() except +

詳しくは Cython から C++ を使う を参照してください。

例外については、いくつか注意点があります:

  • 例外値は、 int, enum, float, ポインタ型を返す関数でのみ宣言でき、値 は定数の式でなければなりません。 void 型の関数では、 except * 形 式しか宣言できません。

  • 例外値の指定は、関数のシグネチャの一部です。関数へのポインタをパラメ タとして渡したり、変数に代入したりしたい場合、関数のパラメタや変数の 宣言と同様に、例外値の型指定も行わねばなりません (あるいは、両方から、 例外値の型指定を除去せねばなりません)。例外値を伴う関数へのポインタ を宣言するときの例を以下に示します:

    int (*grail)(int, char *) except -1
    
  • Python オブジェクトを返す関数に対しては、例外値の宣言を行う必要はあ りません (行なってはなりません)。戻り値の宣言を省略すると、暗黙のう ちに Python オブジェクトを返すということを思い出して下さい (そのよう な関数は、NULL を返すことで、例外の伝播を行なっています)。

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

シグネチャで例外値に指定した値が関数から戻っても、 except 節はエラーを送出しないので、よく注意しましょう。例えば、以下のような書 き方をしておいて、:

cdef extern FILE *fopen(char *filename, char *mode) except NULL # 間違い!

fopen()NULL を返した時に例外が自動的に送出されると期待し ても、そうはいかず、 except 節は何も捕捉しません。 例外値の宣言は、 Cython の関数や、 Python/C APIルーチンの中で送出され た Python の例外を伝播するためのものです。 fopen() のような、 Python 対応でない関数から例外を送出させるには、以下の例のように、自分 で戻り値をチェックして例外を送出する必要があります:

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

自動型変換

ほとんどの場合、基本的な数値型と文字列型については、C の値が必要なコン テキストで Python を使った場合、あるいはその逆の状況で、自動的な変換が 行われます。起き得る変換についてまとめた表を以下に示します。

C の型 変換元の Python 型 変換先の Python 型
[unsigned] char [unsigned] short int, long int, long int
unsigned int unsigned long [unsigned] long long int, long long
float, double, long double int, long, float float
char * str/bytes str/bytes [1]
struct   dict
[1]Python 2.x では str との相互変換で、 Python 3 では bytes です。

Python の文字列を C のコンテキスト下で使うときの注意

char * が必要なコンテキストで Python の文字列型を使うときには注意 が必要です。そのような状況では、 Python 文字列オブジェクトの中のポイン タが使われ、有効なのは文字列オブジェクトが存在している間だけだからです。 そのため、C の文字列が必要な間だけ、元の Python 文字列への参照を維持せ ねばなりません。文字列オブジェクトを十分に維持できる保証がないときには、 C の文字列をコピーせねばなりません。

Cython はこの種の間違いを検出して、防いでくれます。例えば、以下のよう な操作を行おうとすると:

cdef char *s
s = pystring1 + pystring2

Cython は Obtaining char * from temporary Python value というメッ セージを出力します。なぜなら、二つの Python 文字列の結合を行うと、新し い Python の文字列オブジェクトが、 Cython の生成する一時的かつ内部的な 値として生成されるからです。この文の実行が終了すると、この一時的な値は 即座に decref され、Python の文字列は開放されてしまうので、 s はい わゆるぶら下がり (dagling) ポインタになってしまいます。このコードはお そらくうまく動作しないので、 Cython はコンパイルを拒否します。

解決するには、まず文字列を結合した結果を Python の変数に代入して、そ の後で char * に値を取り出します:

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

ただし、必要に応じて p への参照を保持しておくのは、あなたの責任です。

上記のようなエラーの検出に使われているのは、ヒューリスティックな規則だ けだということを覚えておいて下さい。場合によっては、 Cython は不要な警 告を発したり、問題のあるコードの検出に失敗したりします。究極的には、ユー ザが問題を理解して、注意深く操作をする必要があります。

実行文と式

Cython の制御構造と式は、その大半が Python の構文規則に従っています。 Python オブジェクトを扱っている場合には、 (特に注釈の内限り) Python と 同じセマンティクスです。 Python の演算子の大半が C の値に対しても適用 でき、自明なセマンティクスを持っています。

Python オブジェクトと C の値を一つの式で混在させると、 Python オブジェ クトと C の数値型・文字列型との間で値を自動的に変換します。

Python オブジェクトの参照カウントは自動的に管理されます。Python の操作 に対しては自動的にエラーチェックをい、適切なアクションをとります。

C の式と Cython の式の違い

C の式と Cython の式との間、とりわけ C のコンストラクトに対して Python の等価な表現がない部分には、構文とセマンティクスの違いがいくつかあります。

  • 整数リテラルは C の定数として扱われ、 C コンパイラが適切と考えるサイ ズに切り詰められます。Python の整数オブジェクト (任意の桁精度) を生 成したいときは、オブジェクトに即時キャスト (<object>100000000000000000000) します。 L, LL, U サ フィックスの意味は、 C でのサフィックスと同じです。

  • Cython には -> 演算子がありません。 p->x の代わりに p.x を使って下さい。

  • Cython には単項演算子 * がありません。 *p の代わりに p[0] を使って下さい。

  • C と同じセマンティクスの & 演算子はありません。

  • C のヌルポインタは NULL と表記します。 0 を使ってはなりませ ん (NULL は予約語です)。

  • 型キャストは、 <type>value のように書きます:

    cdef char *p, float *q
    p = <char*>q
    

スコープ規則

Cython は、ある変数が、ローカルなスコープ、モジュールスコープ、ビルト インスコープのどこに属しているかを、完全に静的に決定します。 Python と 同じく、特に宣言のないかぎり、あるスコープで変数への代入操作を行うと、 変数は暗黙のうちにそのスコープ上に存在する Python の変数として宣言され ます。

Note

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

try:
    x = True
except NameError:
    True = 1

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

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

ビルトイン関数

Cython は、下記のビルトイン関数への呼び出しを、対応する Python/C API ルーチンの直接呼び出しにコンパイル時に置き換えることで高速化を図ります。

関数と引数 戻り値型 等価な Python/C API 関数
abs(obj) object PyNumber_Absolute
delattr(obj, name) int PyObject_DelAttr
dir(obj) getattr(obj, name) (注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) (注2) object PyNumber_Power
reload(obj) object PyImport_ReloadModule
repr(obj) object PyObject_Repr
setattr(obj, name) void PyObject_SetAttr

注1: Python の getattr() には、第三引数の有無によって二種類の関 数が対応します。 Python のコンテキストでは、どちらの関数も Python の getattr() 関数を表します。

注2: pow() には引数が三つの形式しかありません。それ以外の場合に は ** 演算子を使って下さい。

最適化が行われるのは、上記の名前で関数を直接呼び出したときだけです。こ れらの関数を、直接呼ぶ以外のやり方で、 Python のオブジェクトとして扱っ た場合、たとえば、一旦 Python の変数に代入しておいてから、後で呼び出し た場合などには、通常の Python の関数呼び出しとして実行されます。

演算子の優先順

Python と C は、演算子の優先順位が異なっていて、 Cython では C ではな くPython の優先順位を使っているので注意してください。

整数の for ループ

Cython は、 for-in-range を使ったよくある Python の整数ループをパター ンとして認識します:

for i in range(n):
    ...

icdef の整数型で定義されていると、Cython はこのルー プを pure C のループに最適化します。ループ変数に対する制約があるのは、 そうしないと、コードのターゲットアーキテクチャで整数オーバフローが発生 して、生成したコードがおかしくなる可能性があるからです。ループが正しく 変換されていないかもしれないと思ったら、cython コマンドラインでアノテー ション機能 (-a) オプションを有効にして、生成された C のコードを調 べてみて下さい。 range の自動変換 も参照してください。

Pyrex との後方互換性のために、 Cython には別の形式の for-from 構文があ ります。構文は、以下の形式:

for i from 0 <= i < n:
    ...

または、以下の形式で書きます:

for i from 0 <= i < n by s:
    ...

s はステップサイズを表す整数です。

for-from ループを使うときは、以下の点に注意してください:

  • ターゲットの式は変数名でなければなりません。
  • 下界値と上界値の表現に使う変数名と、ターゲットの変数名は同じにせねば なりません。
  • イテレーションの方向は、関係式によって決まります。関係式が両方とも {<, <=} のいずれかであれば昇順に、 {>, >=} であれば 降順にループします。 (それ以外の組み合わせは使えません。)

他の Python のループ文と同様、 break や continue をループ本体で使えま す。また、ループ文には else 節を定義できます。

include 文

Warning

かつて、 include 文は、宣言をファイル間で共有するのに使われて いました。 Cython モジュール間で宣言を共有する を使うようにして下さい。

include 文を使えば、 Cython のソースファイルに、他のファイルのマテリア ルを include できます。例えば:

include "spamstuff.pxi"

このようにすると、指定した名前のファイルが宣言した場所に原文のまま取り 込まれます。include するファイルには、他の include 文も含め、 include を実行した場所のコンテキストで有効な全ての文や宣言を入れられます。 include するファイルの内容はインデントレベルゼロで始まっていなければな りません。ファイルの内容のインデントレベルは、includeする側で、 include を行った文のインデントレベルに沿って決まります。

Note

Cython コードを複数の部品に分割する方法は他にもあり、多くの場合で そちらの方がより適切です。 Cython モジュール間で宣言を共有する を参照して ください。

条件付きコンパイル

Cython には、条件付きコンパイルやコンパイル時の定数をソースファイルに 定義するための機能がいくつかあります。

コンパイル時の定義

コンパイル時の定義は、 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

以下の組み込み定数および関数を使えます:

None, True, False, abs, bool, chr, cmp, complex, dict, divmod, enumerate, float, hash, hex, int, len, list, long, map, max, min, oct, ord, pow, range, reduce, repr, round, slice, str, sum, tuple, xrange, zip

DEF を使って定義した名前は、識別子 (identifier) を書けるところなら どこにでも記述でき、その名前のコンパイル時の値が、ソースコードの該当箇 所にリテラルとして書き込まれたかのように置き換わります。そのため、コン パイル時の式は、最終的には Python の int, long, float, str のいずれかに評価されなければなりません:

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

条件付きコンパイル文

IF 文は、コードの一部を条件つきで取り込んだり除外したりするのに使 います。 C の #if プリプロセッサディレクティブと同じ働きです:

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 文も含めて何でも書けま す。

IFELIF 節の式は、 DEF 文と同様、有効なコンパイル時評価 式でなければなりませんが、任意の Python の値を評価でき、その真偽値は通 常の Python の方法に従って決まります。