C API の使用: その他の話題¶
チュートリアル では C API 拡張モジュールの作り方を説明しましたが、まだ説明していない領域が数多くあります。 この文書では、より複雑な拡張を書くために必要な概念をいくつか取り上げます。
エラーと例外¶
An important convention throughout the Python interpreter is the following: when
a function fails, it should set an exception condition and return an error value
(usually -1 or a NULL pointer). Exception information is stored in
three members of the interpreter's thread state. These are NULL if
there is no exception. Otherwise they are the C equivalents of the members
of the Python tuple returned by sys.exc_info(). These are the
exception type, exception instance, and a traceback object. It is important
to know about them to understand how errors are passed around.
Python API では、様々な型の例外をセットするための関数をいくつか定義しています。
もっともよく用いられるのは PyErr_SetString() です。引数は例外オブジェクトと C 文字列です。例外オブジェクトは通常、 PyExc_ZeroDivisionError のような定義済みのオブジェクトです。 C 文字列はエラーの原因を示し、Python 文字列オブジェクトに変換されて例外の "付属値" に保存されます。
もう一つ有用な関数として PyErr_SetFromErrno() があります。この関数は引数に例外だけをとり、付属値はグローバル変数 errno から構築します。もっとも汎用的な関数は PyErr_SetObject() で、二つのオブジェクト、例外と付属値を引数にとります。これら関数に渡すオブジェクトには Py_INCREF() を使う必要はありません。
例外がセットされているかどうかは、 PyErr_Occurred() を使って非破壊的に調べられます。この関数は現在の例外オブジェクトを返します。例外が発生していない場合には NULL を返します。通常は、関数の戻り値からエラーが発生したかを判別できるはずなので、 PyErr_Occurred() を呼び出す必要はありません。
別の関数 g を呼び出した関数 f が、その呼び出しの失敗を検出した場合、f 自身もエラー値 (通常は NULL または -1) を返す必要があります。
このとき PyErr_* 関数を呼び出しては いけません。
g がすでに呼び出しているからです。
次に f の呼び出し元も、PyErr_* を 呼び出さずに、その呼び出し元 へエラーを示す値を返します。
以下同様に続きます。
エラーの最も詳しい原因は、最初にエラーを検出した関数がすでに報告しています。
エラーが Python インタープリタのメインループに到達すると、実行中の Python コードの処理を中断し、Python プログラマーが指定した例外ハンドラーを探します。
(別の PyErr_* 関数を呼び出すことで、モジュールがより詳しいエラーメッセージを提供できる場合は、そうしても構いません。
ただし、一般には不要であり、エラーの原因に関する情報が失われるおそれがあります。
ほとんどの操作は様々な理由で失敗し得るためです。)
ある関数呼び出しでの処理の失敗によってセットされた例外を無視するには、 PyErr_Clear() を呼び出して例外状態を明示的に消去しなくてはなりません。エラーをインタプリタには渡したくなく、自前で (何か他の作業を行ったり、何も起こらなかったかのように見せかけるような) エラー処理を完全に行う場合にのみ、 PyErr_Clear() を呼び出すようにすべきです。
malloc() の呼び出し失敗は、常に例外にしなくてはなりません --- malloc() (または realloc()) を直接呼び出しているコードは、 PyErr_NoMemory() を呼び出して、失敗を示す値を返さねばなりません。オブジェクトを生成する全ての関数 (例えば PyLong_FromLong()) は PyErr_NoMemory() の呼び出しを済ませてしまうので、この規則が関係するのは直接 malloc() を呼び出すコードだけです。
また、PyArg_ParseTuple() などの重要な例外を除き、整数の状態コードを返す関数は通常、Unix のシステムコールと同じく、成功時に正の値またはゼロを返し、失敗時に -1 を返します。
最後に、エラー標示値を返す際に、(エラーが発生するまでに既に生成してしまったオブジェクトに対して Py_XDECREF() や Py_DECREF() を呼び出して) ごみ処理を注意深く行ってください!
どの例外を送出するかは自由に選べます。
PyExc_ZeroDivisionError など、Python のすべての組み込み例外に対応する C オブジェクトが宣言済みで、そのまま利用できます。
もちろん、例外は適切に選ぶ必要があります。
例えば、ファイルを開けなかったことを示すために PyExc_TypeError を使うべきではありません (おそらく PyExc_OSError が適切です)。
引数リストに問題がある場合、PyArg_ParseTuple() 関数は通常 PyExc_TypeError を送出します。
引数の値が特定の範囲内でなければならない場合や、その他の条件を満たす必要がある場合は、PyExc_ValueError が適切です。
モジュール独自の新しい例外を定義することもできます。 最も簡単な方法は、ファイルの先頭で静的なグローバルオブジェクト変数を宣言することです。
static PyObject *SpamError = NULL;
そして、モジュールの Py_mod_exec 関数 (spam_module_exec()) で PyErr_NewException() を呼び出して初期化します。
SpamError = PyErr_NewException("spam.error", NULL, NULL);
SpamError はグローバル変数なので、モジュールが再初期化されて Py_mod_exec 関数が呼び出されるたびに上書きされます。
ひとまず、この問題は回避しましょう。
ImportError を送出して、初期化が繰り返されるのを防ぎます。
static PyObject *SpamError = NULL;
static int
spam_module_exec(PyObject *m)
{
if (SpamError != NULL) {
PyErr_SetString(PyExc_ImportError,
"cannot initialize spam module more than once");
return -1;
}
SpamError = PyErr_NewException("spam.error", NULL, NULL);
if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) {
return -1;
}
return 0;
}
static PyModuleDef_Slot spam_module_slots[] = {
{Py_mod_exec, spam_module_exec},
{0, NULL}
};
static struct PyModuleDef spam_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "spam",
.m_size = 0, // 非負の値
.m_slots = spam_module_slots,
};
PyMODINIT_FUNC
PyInit_spam(void)
{
return PyModuleDef_Init(&spam_module);
}
例外オブジェクトの Python での名前は spam.error であることに注意してください。
PyErr_NewException() 関数は、NULL の代わりに別のクラスを渡さなければ、組み込み例外 で説明する Exception を基底クラスとするクラスを作成できます。
SpamError 変数が新しく作成した例外クラスへの参照を保持している点にも注意してください。
これは意図的なものです。
外部のコードがモジュールから例外を削除する可能性があるため、クラスへの参照を所有しておく必要があります。
そうすることで、クラスが破棄されて SpamError がダングリングポインターになるのを防ぎます。
ダングリングポインターになると、その例外を送出する C コードがコアダンプなどの意図しない副作用を引き起こすおそれがあります。
現時点では、この参照を解放する Py_DECREF() 呼び出しがありません。
Python インタープリタが終了しても、グローバル変数 SpamError はガベージコレクションの対象にならず、「リーク」します。
ただし、これが起こるのはプロセスあたり最大 1 回となるようにしてあります。
We discuss the use of PyMODINIT_FUNC as a function return type later in this
sample.
次のように PyErr_SetString() を呼び出すと、拡張モジュールから spam.error 例外を送出できます。
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
if (sts < 0) {
PyErr_SetString(SpamError, "System command failed");
return NULL;
}
return PyLong_FromLong(sts);
}
拡張モジュールの組み込み¶
モジュールを Python インタープリタの恒久的な構成要素にするには、設定を変更してインタープリタを再ビルドする必要があります。
Unix では、ソース配布物を展開した Modules/ ディレクトリにファイル (例えば spammodule.c) を置き、Modules/Setup.local にそのファイルを記述する行を追加します。
spam spammodule.o
を追加して、トップレベルのディレクトリで make を実行して、インタプリタを再ビルドするだけです。 Modules/ サブディレクトリでも make を実行できますが、前もって 'make Makefile' を実行して Makefile を再ビルドしておかなければならりません。(この作業は Setup ファイルを変更するたびに必要です。)
モジュールが別のライブラリとリンクされている必要がある場合、ライブラリも設定ファイルに列挙できます。例えば以下のようにします。
spam spammodule.o -lX11
C から Python 関数を呼び出す¶
チュートリアルでは、Python から C 関数を呼び出せるようにする方法に注目しました。 その逆、つまり C から Python 関数を呼び出すことも有用です。 特に、いわゆる「コールバック」関数をサポートするライブラリでは役立ちます。 C のインターフェースがコールバックを利用する場合、それに対応する Python のインターフェースでも、Python プログラマーにコールバックの仕組みを提供する必要があることがよくあります。 その実装には、C のコールバックから Python のコールバック関数を呼び出す処理が必要です。 ほかの用途も考えられます。
幸い、Python インタープリタは簡単に再帰的に呼び出せますし、Python 関数を呼び出すための標準インターフェースがあります。 (特定の文字列を入力として Python のパーサーを呼び出す方法については、超高水準レイヤ を参照してください。)
Python 関数の呼び出しは簡単です。まず、C のコードに対してコールバックを登録しようとする Python プログラムは、何らかの方法で Python の関数オブジェクトを渡さねばなりません。このために、コールバック登録関数 (またはその他のインターフェース) を提供せねばなりません。このコールバック登録関数が呼び出された際に、引き渡された Python 関数オブジェクトへのポインタをグローバル変数に --- あるいは、どこか適切な場所に --- 保存します (関数オブジェクトを Py_INCREF() するようよく注意してください!)。例えば、以下のような関数がモジュールの一部になっていることでしょう:
static PyObject *my_callback = NULL;
static PyObject *
my_set_callback(PyObject *dummy, PyObject *args)
{
PyObject *result = NULL;
PyObject *temp;
if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
if (!PyCallable_Check(temp)) {
PyErr_SetString(PyExc_TypeError, "parameter must be callable");
return NULL;
}
Py_XINCREF(temp); /* 新しいコールバックへの参照を追加する */
Py_XDECREF(my_callback); /* 以前のコールバックを解放する */
my_callback = temp; /* 新しいコールバックを保存する */
/* "None" を返すための定型処理 */
Py_INCREF(Py_None);
result = Py_None;
}
return result;
}
この関数は、PyMethodDef.ml_flags に METH_VARARGS フラグを指定してインタープリタに登録する必要があります。
PyArg_ParseTuple() 関数とその引数については 拡張モジュール関数でのパラメタ展開 で説明しています。
Py_XINCREF() および Py_XDECREF() は、オブジェクトに対する参照カウントをインクリメント/デクリメントするためのマクロで、 NULL ポインタが渡されても安全に操作できる形式です (とはいえ、上の流れでは temp が NULL になることはありません)。これらのマクロと参照カウントについては、 参照カウント法 で説明しています。
その後、コールバック関数を呼び出す時が来たら、C 関数 PyObject_CallObject() を呼び出します。この関数には二つの引数: Python 関数と Python 関数の引数リストがあり、いずれも任意の Python オブジェクトを表すポインタ型です。引数リストは常にタプルオブジェクトでなければならず、その長さは引数の数になります。Python 関数を引数なしで呼び出すのなら、 NULL か空のタプルを渡します; 単一の引数で関数を呼び出すのなら、単要素 (singleton) のタプルを渡します。 Py_BuildValue() の書式文字列中に、ゼロ個または一個以上の書式化コードが入った丸括弧がある場合、この関数はタプルを返します。以下に例を示します:
int arg;
PyObject *arglist;
PyObject *result;
...
arg = 123;
...
/* コールバックを呼び出す */
arglist = Py_BuildValue("(i)", arg);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
PyObject_CallObject() は Python オブジェクトへのポインタを返します: これは Python 関数からの戻り値になります。 PyObject_CallObject() は、引数に対して "参照カウント中立 (reference-count- neutral)" です。上の例ではタプルを生成して引数リストとして提供しており、このタプルは PyObject_CallObject() の呼び出し直後に Py_DECREF() されています。
PyObject_CallObject() の戻り値は「新しい参照」です。
新しく作成したオブジェクトか、参照カウントを増やした既存のオブジェクトを指します。
そのため、グローバル変数に保存するのでなければ、結果に対してどこかで Py_DECREF() を呼び出す必要があります。
戻り値を使わない場合も (特にそのような場合こそ) 必要です。
ただし、その前に戻り値が NULL でないことを確認する必要があります。
NULL なら、Python 関数は例外を送出して終了しています。
PyObject_CallObject() を呼び出した C コード自体が Python から呼び出されている場合は、Python の呼び出し元にエラーを示す値を返す必要があります。
これにより、インタープリタがスタックトレースを表示したり、呼び出し元の Python コードが例外を処理したりできます。
そうすることが不可能、または望ましくない場合は、PyErr_Clear() を呼び出して例外を消去する必要があります。
例えば、次のようにします。
if (result == NULL)
return NULL; /* 呼び出し元にエラーを返す */
...use result...
Py_DECREF(result);
Python のコールバック関数にどのようなインターフェースを用意するかによっては、PyObject_CallObject() に引数リストを渡す必要もあります。
コールバック関数を指定するインターフェースを通じて、Python プログラムが引数リストも渡す場合があります。
その場合は、関数オブジェクトと同じ方法で保存して使えます。
一方、引数リストとして渡す新しいタプルを構築しなければならない場合もあります。
最も簡単な方法は Py_BuildValue() を呼び出すことです。
例えば、整数のイベントコードを渡すには、次のように書けます。
PyObject *arglist;
...
arglist = Py_BuildValue("(l)", eventcode);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
return NULL; /* 呼び出し元にエラーを返す */
/* 必要ならここで結果を使う */
Py_DECREF(result);
Py_DECREF(arglist) が呼び出しの直後、エラーチェックよりも前に置かれていることに注意してください! また、厳密に言えば、このコードは完全ではありません: Py_BuildValue() はメモリ不足におちいるかもしれず、チェックしておくべきです。
位置引数とキーワード引数をサポートする PyObject_Call() を使うと、キーワード引数を指定して関数を呼び出すこともできます。
上の例と同じように、Py_BuildValue() を使って辞書を構築します。
PyObject *dict;
...
dict = Py_BuildValue("{s:i}", "name", val);
result = PyObject_Call(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);
拡張モジュール関数でのパラメタ展開¶
チュートリアル では「METH_O」関数を使いましたが、これは Python の引数を 1 個だけ受け取れます。
より多くの引数が必要なら、代わりに METH_VARARGS を使えます。
このフラグを指定すると、C 関数は単一のオブジェクトではなく、引数の tuple を受け取ります。
タプルの要素を取り出すために、CPython は次のように宣言された PyArg_ParseTuple() 関数を提供しています。
int PyArg_ParseTuple(PyObject *arg, const char *format, ...);
引数 arg は、Python から C 関数に渡された引数リストを含むタプルオブジェクトでなければなりません。 引数 format は書式文字列で、その構文は Python/C API リファレンスマニュアルの 引数の解釈と値の構築 で説明しています。 残りの引数には、書式文字列によって型が決まる変数のアドレスを渡す必要があります。
例えば、Python の str オブジェクトを 1 個受け取って C のバッファーに変換するには、書式文字列に "s" を指定します。
const char *command;
if (!PyArg_ParseTuple(args, "s", &command)) {
return NULL;
}
If an error is detected in the argument list, PyArg_ParseTuple()
returns NULL (the error indicator for functions returning object pointers);
your function may return NULL, relying on the exception set by
PyArg_ParseTuple().
PyArg_ParseTuple() は Python 側から与えられた引数が必要な型になっているか調べるのに対し、 PyArg_ParseTuple() は呼び出しの際に渡された C 変数のアドレスが有効な値を持つか調べられないことに注意してください: ここで間違いを犯すと、コードがクラッシュするかもしれませんし、少なくともでたらめなビットをメモリに上書きしてしまいます。慎重に!
呼び出し側に提供されるオブジェクトへの参照はすべて 借用 参照 (borrowed reference) になります; これらのオブジェクトの参照カウントをデクリメントしてはなりません!
以下にいくつかの呼び出し例を示します:
#include <Python.h>
int ok;
int i, j;
long k, l;
const char *s;
Py_ssize_t size;
ok = PyArg_ParseTuple(args, ""); /* 引数なし */
/* Python での呼び出し: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* 文字列 */
/* Python での呼び出し例: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* long 型の値 2 個と文字列 */
/* Python での呼び出し例: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
/* int 型の値のペアと文字列。
文字列のサイズも取得する */
/* Python での呼び出し例: f((1, 2), 'three') */
{
const char *file;
const char *mode = "r";
int bufsize = 0;
ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
/* 文字列と、省略可能な別の文字列および整数 */
/* Python での呼び出し例:
f('spam')
f('spam', 'w')
f('spam', 'wb', 100000) */
}
{
int left, top, right, bottom, h, v;
ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
&left, &top, &right, &bottom, &h, &v);
/* 長方形と点 */
/* Python での呼び出し例:
f(((0, 0), (400, 300)), (10, 10)) */
}
{
Py_complex c;
ok = PyArg_ParseTuple(args, "D:myfunction", &c);
/* 複素数。
エラー用の関数名も指定する */
/* Python での呼び出し例: myfunction(1+2j) */
}
拡張モジュール関数のキーワードパラメタ¶
関数が キーワード引数 も受け取れるようにするには、METH_VARARGS と組み合わせて METH_KEYWORDS フラグを使います。
(METH_KEYWORDS はほかのフラグとも組み合わせられます。
使用できる組み合わせについては、そのドキュメントを参照してください。)
この場合、C 関数はキーワード引数の辞書を受け取るための 3 番目の引数を PyObject * 型で受け取る必要があります。
このような関数の引数を解析するには PyArg_ParseTupleAndKeywords() を使います。
PyArg_ParseTupleAndKeywords() は、以下のように宣言されています:
int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
const char *format, char * const *kwlist, ...);
arg と format パラメタは PyArg_ParseTuple() のものと同じです。 kwdict パラメタはキーワード引数の入った辞書で、 Python ランタイムシステムから第三パラメタとして受け取ります。 kwlist パラメタは各パラメタを識別するための文字列からなる、 NULL 終端されたリストです; 各パラメタ名は format 中の型情報に対して左から右の順に照合されます。成功すると PyArg_ParseTupleAndKeywords() は真を返し、それ以外の場合には適切な例外を送出して偽を返します。
注釈
キーワード引数を使っている場合、タプルは入れ子にして使えません! kwlist 内に存在しないキーワードパラメタが渡された場合、 TypeError の送出を引き起こします。
以下にキーワードを使ったモジュール例を示します。これは Geoff Philbrick (philbrick@hks.com) によるプログラム例をもとにしています:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
{
int voltage;
const char *state = "a stiff";
const char *action = "voom";
const char *type = "Norwegian Blue";
static char *kwlist[] = {"voltage", "state", "action", "type", NULL};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,
&voltage, &state, &action, &type))
return NULL;
printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
action, voltage);
printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);
Py_RETURN_NONE;
}
static PyMethodDef keywdarg_methods[] = {
/* PyCFunction は PyObject* 引数を 2 個だけ受け取るのに対し、
* keywdarg_parrot() は 3 個受け取るため、関数のキャストが必要。
*/
{"parrot", (PyCFunction)(void(*)(void))keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
"楽しい寸劇を標準出力に表示します。"},
{NULL, NULL, 0, NULL} /* 終端 */
};
任意の値を構築する¶
Py_BuildValue() は PyArg_ParseTuple() の対極に位置するものです。この関数は以下のように定義されています:
PyObject *Py_BuildValue(const char *format, ...);
Py_BuildValue() は、 PyArg_ParseTuple() の認識する一連の書式単位に似た書式単位を認識します。ただし (関数への出力ではなく、入力に使われる) 引数はポインタではなく、ただの値でなければなりません。 Python から呼び出された C 関数が返す値として適切な、新たな Python オブジェクトを返します。
PyArg_ParseTuple() とは一つ違う点があります: PyArg_ParseTuple() は第一引数をタプルにする必要があります (Python の引数リストは内部的には常にタプルとして表現されるからです) が、 Py_BuildValue() はタプルを生成するとは限りません。 Py_BuildValue() は書式文字列中に書式単位が二つかそれ以上入っている場合にのみタプルを構築します。書式文字列が空なら、 None を返します。きっかり一つの書式単位なら、その書式単位が記述している何らかのオブジェクトになります。サイズが 0 や 1 のタプル返させたいのなら、書式文字列を丸括弧で囲います。
以下に例を示します (左に呼び出し例を、右に構築される Python 値を示します):
Py_BuildValue("") None
Py_BuildValue("i", 123) 123
Py_BuildValue("iii", 123, 456, 789) (123, 456, 789)
Py_BuildValue("s", "hello") 'hello'
Py_BuildValue("y", "hello") b'hello'
Py_BuildValue("ss", "hello", "world") ('hello', 'world')
Py_BuildValue("s#", "hello", 4) 'hell'
Py_BuildValue("y#", "hello", 4) b'hell'
Py_BuildValue("()") ()
Py_BuildValue("(i)", 123) (123,)
Py_BuildValue("(ii)", 123, 456) (123, 456)
Py_BuildValue("(i,i)", 123, 456) (123, 456)
Py_BuildValue("[i,i]", 123, 456) [123, 456]
Py_BuildValue("{s:i,s:i}",
"abc", 123, "def", 456) {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",
1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))
参照カウント法¶
C や C++のような言語では、プログラマはヒープ上のメモリを動的に確保したり解放したりする責任があります。こうした作業は C では関数 malloc() や free() で行います。C++では本質的に同じ意味で演算子 new や delete が使われます。そこで、以下の議論は C の場合に限定して行います。
malloc() で確保したメモリブロックは、最終的に free() をちょうど 1 回呼び出して、利用可能なメモリのプールに戻す必要があります。
free() を適切なタイミングで呼び出すことが重要です。
free() を呼び出さないままブロックのアドレスを失うと、そのメモリはプログラムが終了するまで再利用できません。
これをメモリリーク (memory leak) と呼びます。
一方、free() を呼び出した後もそのブロックを使い続けると、別の malloc() 呼び出しによるブロックの再利用と競合します。
これを解放済みメモリの使用 (using freed memory) と呼びます。
初期化していないデータへの参照と同様に、コアダンプ、誤った結果、不可解なクラッシュといった悪影響があります。
よくあるメモリリークの原因はコード中の普通でない処理経路です。例えば、ある関数があるメモリブロックを確保し、何らかの計算を行って、再度ブロックを解放するとします。さて、関数の要求仕様を変更して、計算に対するテストを追加すると、エラー条件を検出し、関数の途中で処理を戻すようになるかもしれません。この途中での終了が起きるとき、確保されたメモリブロックは解放し忘れやすいのです。コードが後で追加された場合には特にそうです。このようなメモリリークが一旦紛れ込んでしまうと、長い間検出されないままになることがよくあります: エラーによる関数の終了は、全ての関数呼び出しのに対してほんのわずかな割合しか起きず、その一方でほとんどの近代的な計算機は相当量の仮想記憶を持っているため、メモリリークが明らかになるのは、長い間動作していたプロセスがリークを起こす関数を何度も使った場合に限られるからです。従って、この種のエラーを最小限にとどめるようなコーディング規約や戦略を設けて、不慮のメモリリークを避けることが重要なのです。
Python は malloc() や free() を非常によく利用するため、メモリリークの防止に加え、解放されたメモリの使用を防止する戦略が必要です。このために選ばれたのが参照カウント法 (reference counting) と呼ばれる手法です。参照カウント法の原理は簡単です: 全てのオブジェクトにはカウンタがあり、オブジェクトに対する参照がどこかに保存されたらカウンタをインクリメントし、オブジェクトに対する参照が削除されたらデクリメントします。カウンタがゼロになったら、オブジェクトへの最後の参照が削除されたことになり、オブジェクトは解放されます。
別の方法として、自動ガベージコレクション (automatic garbage collection) があります。
(参照カウント法もガベージコレクションの方法と呼ばれることがあるため、両者を区別するために「自動」と付けています。)
自動ガベージコレクションの大きな利点は、利用者が free() を明示的に呼び出す必要がないことです。
(速度やメモリ使用量の改善も利点として挙げられますが、これは確実な事実ではありません。)
欠点は、C には真に移植性のある自動ガベージコレクターがないことです。
一方、参照カウント法は移植可能な形で実装できます (malloc() と free() が利用できる限りですが、これは C 規格で保証されています)。
いつかは C でも十分に移植性のある自動ガベージコレクターが使えるようになるかもしれません。
それまでは参照カウントを使っていく必要があります。
Python は従来の参照カウント法に加えて、循環参照を検出する仕組みも備えています。 これにより、アプリケーションは直接または間接的な循環参照ができることを心配せずに済みます。 循環参照は、参照カウント法だけで実装したガベージコレクションの弱点です。 循環参照は、直接または間接的に自分自身への参照を持つオブジェクトからなり、循環内の各オブジェクトの参照カウントはゼロではありません。 通常の参照カウント法の実装では、循環そのものへの参照がほかになくなっても、循環内のオブジェクトや、そこから参照されるオブジェクトのメモリを回収できません。
循環参照検出機構はそのようなガベージサイクル (前述したような循環参照オブジェクト) を検出して回収することができます。
gc モジュールそのような検出機構の実行 (collect() 関数) を提供するとともに、設定のためのインタフェースおよび検出機構を実行時に無効にする方法も提供しています。
Python における参照カウント法¶
参照カウントを増減するマクロとして、Py_INCREF(x) と Py_DECREF(x) があります。
Py_DECREF() は、カウントがゼロになるとオブジェクトの解放も行います。
柔軟性を持たせるため、free() を直接呼び出すのではなく、オブジェクトの型オブジェクト (type object) にある関数ポインターを介して呼び出します。
この目的などのために、すべてのオブジェクトは自身の型オブジェクトへのポインターも持っています。
さて、まだ重大な疑問が残っています: いつ Py_INCREF(x) や Py_DECREF(x) を使えばよいのでしょうか? まず、いくつかの用語説明から始めさせてください。まず、オブジェクトは "占有 (own)" されることはありません; しかし、あるオブジェクトに対する参照の所有 own a reference はできます。オブジェクトの参照カウントは、そのオブジェクトが参照の所有を受けている回数と定義されています。参照の所有者は、参照が必要なくなった際に Py_DECREF() を呼び出す役割を担います。参照の所有権は委譲 (transfer) できます。所有参照 (owned reference) の放棄には、渡す、保存する、 Py_DECREF() を呼び出す、という三つの方法があります。所有参照を処理し忘れると、メモリリークを引き起こします。
オブジェクトへの参照を借用 (borrow) [1] することもできます。
参照を借用した側は Py_DECREF() を呼び出してはいけません。
また、借用元の所有者よりも長くオブジェクトを保持してはいけません。
所有者が参照を手放した後に借用参照を使うと、解放済みメモリを使うおそれがあるため、絶対に避けてください [2]。
参照の借用が参照の所有よりも優れている点は、コードがとりうるあらゆる処理経路で参照を廃棄しておくよう注意しなくて済むことです --- 別の言い方をすれば、借用参照の場合には、処理の途中で関数を終了してもメモリリークの危険を冒すことがない、ということです。逆に、所有よりも不利な点は、ごくまともに見えるコードが、実際には参照の借用元で放棄されてしまった後にその参照を使うかもしれないような微妙な状況があるということです。
Py_INCREF() を呼び出すと、借用参照を所有参照に変更できます。この操作は参照の借用元の状態には影響しません --- Py_INCREF() は新たな所有参照を生成し、参照の所有者が担うべき全ての責任を課します (つまり、新たな参照の所有者は、以前の所有者と同様、参照の放棄を適切に行わねばなりません)。
所有権にまつわる規則¶
オブジェクトへの参照を関数に渡す場合も、関数から返す場合も、その参照の所有権を一緒に移すかどうかは、関数のインターフェース仕様の一部です。
オブジェクトへの参照を返すほとんどの関数は、参照とともに所有権も渡します。特に、 PyLong_FromLong() や Py_BuildValue() のように、新しいオブジェクトを生成する関数は全て所有権を相手に渡します。オブジェクトが実際には新たなオブジェクトでなくても、そのオブジェクトに対する新たな参照の所有権を得ます。例えば、 PyLong_FromLong() はよく使う値をキャッシュしており、キャッシュされた値への参照を返すことがあります。
ほかのオブジェクトからオブジェクトを取り出す多くの関数も、参照とともに所有権を移します。
例えば PyObject_GetAttrString() がそうです。
ただし、こちらはよく使う関数にも例外があり、少し複雑です。
PyTuple_GetItem()、PyList_GetItem()、PyDict_GetItem()、PyDict_GetItemString() はいずれも、タプル、リスト、辞書から借用した参照を返します。
PyImport_AddModule() は、実際にはオブジェクトを生成して返すことがあるにもかかわらず、借用参照を返します: これが可能なのは、生成されたオブジェクトに対する所有参照は sys.modules に保持されるからです。
オブジェクトへの参照を別の関数に渡す場合、一般的には、関数側は呼び出し手から参照を借用します --- 参照を保存する必要があるなら、関数側は Py_INCREF() を呼び出して独立した所有者になります。とはいえ、この規則には二つの重要な例外: PyTuple_SetItem() と PyList_SetItem() があります。これらの関数は、渡された引数要素に対して所有権を乗っ取り (take over) ます --- たとえ失敗してもです! (PyDict_SetItem() とその仲間は所有権を乗っ取りません --- これらはいわば "普通の" 関数です。)
Python から C 関数が呼び出される際には、C 関数は呼び出し側から引数への参照を借用します。C 関数の呼び出し側はオブジェクトへの参照を所有しているので、借用参照の生存期間が保証されるのは関数が処理を返すまでです。このようにして借用参照を保存したり他に渡したりしたい場合にのみ、 Py_INCREF() を使って所有参照にする必要があります。
Python から呼び出された C 関数が返す参照は所有参照でなければなりません --- 所有権は関数から呼び出し側へと委譲されます。
薄氷¶
数少ない状況において、一見無害に見える借用参照の利用が問題をひきおこすことがあります。この問題はすべて、インタプリタが非明示的に呼び出され、インタプリタが参照の所有者に参照を放棄させてしまう状況と関係しています。
知っておくべきケースのうち最初の、そして最も重要なものは、リスト要素に対する参照を借りている際に起きる、関係ないオブジェクトに対する Py_DECREF() の使用です。例えば:
void
bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0);
PyList_SetItem(list, 1, PyLong_FromLong(0L));
PyObject_Print(item, stdout, 0); /* 不具合あり! */
}
上の関数はまず、 list[0] への参照を借用し、次に list[1] を値 0 で置き換え、最後にさきほど借用した参照を出力しています。何も問題ないように見えますね? でもそうではないのです!
PyList_SetItem() の中での処理を追ってみましょう。
リストはすべての要素への参照を所有しているため、インデックス 1 の要素を置き換える際には、元の要素への参照を手放す必要があります。
元の要素がユーザー定義クラスのインスタンスで、そのクラスに __del__() メソッドが定義されているとします。
このインスタンスの参照カウントが 1 なら、参照を手放すことで __del__() メソッドが呼び出されます。
内部では、PyList_SetItem() が置き換えた要素に対して Py_DECREF() を呼び出し、その要素に対応する tp_dealloc 関数が呼び出されます。
解放処理中に tp_dealloc は tp_finalize を呼び出します。
クラスのインスタンスでは、これは __del__() メソッドに対応します (PEP 442 を参照)。
この一連の処理はすべて、PyList_SetItem() の呼び出し内で同期的に行われます。
__del__() メソッドは Python で書かれているため、任意の Python コードを実行できます。
では、bug() 内の item への参照を無効にすることもできるでしょうか。
もちろんできます。
bug() に渡されたリストに __del__() メソッドからアクセスできれば、del list[0] に相当する文を実行できます。
これがそのオブジェクトへの最後の参照であれば、オブジェクトのメモリが解放され、item は無効になります。
問題の原因が分かれば、解決は簡単です。一時的に参照回数を増やせばよいのです。正しく動作するバージョンは以下のようになります:
void
no_bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0);
Py_INCREF(item);
PyList_SetItem(list, 1, PyLong_FromLong(0L));
PyObject_Print(item, stdout, 0);
Py_DECREF(item);
}
これは実際にあった話です。
古いバージョンの Python にはこの種の不具合があり、__del__() メソッドが失敗する理由を突き止めるために、C のデバッガーでかなりの時間を費やした人がいました。
借用参照で問題になる 2 つ目のケースは、スレッドが関わるものです。
通常、Python のオブジェクト全体を保護する グローバルロック があるため、インタープリタ内の複数のスレッドが互いに干渉することはありません。
しかし、マクロ Py_BEGIN_ALLOW_THREADS でこのロックを一時的に解放し、Py_END_ALLOW_THREADS で再取得できます。
これは、ブロックする I/O 呼び出しの前後でよく使われます。
I/O の完了を待つ間、ほかのスレッドがプロセッサーを使えるようにするためです。
明らかに、次の関数にも先ほどと同じ問題があります。
void
bug(PyObject *list)
{
PyObject *item = PyList_GetItem(list, 0);
Py_BEGIN_ALLOW_THREADS
...some blocking I/O call...
Py_END_ALLOW_THREADS
PyObject_Print(item, stdout, 0); /* 不具合あり! */
}
NULL ポインタ¶
一般論として、オブジェクトへの参照を引数にとる関数はユーザが NULL ポインタを渡すとは予想しておらず、渡そうとするとコアダンプになる (か、あとでコアダンプを引き起こす) ことでしょう。一方、オブジェクトへの参照を返すような関数は一般に、例外の発生を示す場合にのみ NULL を返します。引数に対して NULL テストを行わない理由は、関数はしばしば受け取ったオブジェクトを他の関数へと引き渡すからです --- 各々の関数が NULL テストを行えば、冗長なテストが大量に行われ、コードはより低速に動くことになります。
従って、 NULL のテストはオブジェクトの "発生源"、すなわち値が NULL になるかもしれないポインタを受け取ったときだけにしましょう。 malloc() や、例外を送出する可能性のある関数がその例です。
マクロ Py_INCREF() および Py_DECREF() は NULL ポインタのチェックを行いません --- しかし、これらのマクロの変化形である Py_XINCREF() および Py_XDECREF() はチェックを行います。
特定のオブジェクト型について調べるマクロ (Pytype_Check()) は NULL ポインタのチェックを行いません --- 繰り返しますが、様々な異なる型を想定してオブジェクトの型を調べる際には、こうしたマクロを続けて呼び出す必要があるので、個別に NULL ポインタのチェックをすると冗長なテストになってしまうのです。型を調べるマクロには、 NULL チェックを行う変化形はありません。
C 関数の呼び出しの仕組みにより、C 関数に渡される引数リスト (例の args) は決して NULL にならず、常にタプルであることが保証されます [3]。
NULL ポインタを Python ユーザレベルに "逃がし" てしまうと、深刻なエラーを引き起こします。
C++での拡張モジュール作成¶
C++ でも拡張モジュールを作成できますが、いくつか制限があります。
メインプログラム (Python インタープリタ) を C コンパイラでコンパイルしてリンクする場合、コンストラクターを持つグローバルオブジェクトや静的オブジェクトは使えません。
メインプログラムを C++ コンパイラでリンクする場合は問題ありません。
Python インタープリタから呼び出される関数 (特にモジュール初期化関数) は、extern "C" を使って宣言する必要があります。
Python のヘッダーファイルを extern "C" {...} で囲む必要はありません。
シンボル __cplusplus が定義されていれば、ヘッダーファイル自体がこの形式を使います (最近の C++ コンパイラはすべてこのシンボルを定義します)。
拡張モジュールに C API を提供する¶
多くの拡張モジュールは単に Python から使える新たな関数や型を提供するだけですが、時に拡張モジュール内のコードが他の拡張モジュールでも便利なことがあります。例えば、あるモジュールでは順序概念のないリストのように動作する "コレクション (collection)" クラスを実装しているかもしれません。ちょうどリストを生成したり操作したりできる C API を備えた標準の Python リスト型のように、この新たなコレクション型も他の拡張モジュールから直接操作できるようにするには一連の C 関数を持っていなければなりません。
一見すると簡単そうです。
関数を書き (もちろん static は付けずに)、適切なヘッダーファイルを用意し、C API の使い方を文書化すればよさそうです。
実際、すべての拡張モジュールを常に Python インタープリタに静的にリンクするのであれば、この方法で動作します。
しかし、モジュールを共有ライブラリとして使う場合、あるモジュールで定義したシンボルが別のモジュールから見えるとは限りません。
可視性の詳細はオペレーティングシステムによって異なります。
Python インタープリタとすべての拡張モジュールで単一のグローバルな名前空間を使うシステムもあれば (例えば Windows)、
モジュールのリンク時にインポートするシンボルを明示的に列挙する必要があるシステム (例えば AIX) や、
複数の方式から選べるシステム (ほとんどの Unix) もあります。
そして、シンボルがどこからでも見える場合でも、呼び出したい関数のあるモジュールがまだロードされていないかもしれません。
そのため、移植性を確保するにはシンボルの可視性について何も仮定しないことが必要です。
つまり、ほかの拡張モジュールとの名前の衝突を避けるため、モジュール初期化関数以外のすべてのシンボルを static として宣言するべきです。
また、ほかの拡張モジュールからアクセス できるようにしたい シンボルは、別の方法で公開する必要があります。
Python は、拡張モジュール間で C レベルの情報 (ポインター) を渡すための特別な仕組みとして Capsule を提供しています。 Capsule はポインター (void*) を格納する Python のデータ型です。 作成や内容へのアクセスは C API 経由でしかできませんが、ほかの Python オブジェクトと同様に受け渡せます。 特に、拡張モジュールの名前空間にある名前に代入できます。 ほかの拡張モジュールはそのモジュールをインポートし、その名前に対応する値を取得して、Capsule の中からポインターを取り出せます。
Capsule を使って拡張モジュールの C API を公開する方法はいくつもあります。 関数ごとに専用の Capsule を用意してもよいですし、すべての C API ポインターを配列に格納し、その配列のアドレスを Capsule で公開しても構いません。 ポインターの保存や取得に関わる処理を、コードを提供するモジュールとクライアントモジュールにどう分担させるかにも、様々な方法があります。
どの方法を選ぶにしても、 Capsule の name を正しく設定することは重要です。 PyCapsule_New() は name 引数 (const char*) を取ります。 NULL を name に渡すことも許可されていますが、 name を設定することを強く推奨します。正しく名前を付けられた Capsule はある程度の実行時型安全性を持ちます。名前を付けられていない Capsule を他の Capsule と区別する現実的な方法はありません。
特に、 C API を公開するための Capsule には次のルールに従った名前を付けるべきです:
modulename.attributename
PyCapsule_Import() という便利関数は、 Capsule の名前がこのルールに一致しているときにのみ、簡単に Capsule 経由で公開されている C API をロードすることができます。この挙動により、 C API のユーザーが、確実に正しい C API を格納している Capsule をロードできたことを確かめることができます。
以下の例では、名前を公開するモジュールの作者にほとんどの負荷が掛かりますが、よく使われるライブラリを作る際に適切なアプローチを実演します。このアプローチでは、全ての C API ポインタ (例中では一つだけですが!) を、 Capsule の値となる void ポインタの配列に保存します。拡張モジュールに対応するヘッダファイルは、モジュールの import と C API ポインタを取得するよう手配するマクロを提供します; クライアントモジュールは、C API にアクセスする前にこのマクロを呼ぶだけです。
API を公開するモジュールは、チュートリアル の spam モジュールを変更したものです。
spam.system() は C ライブラリの system() を直接呼ばず、PySpam_System() を呼び出します。
実際には、この関数はもっと複雑な処理 (例えばすべてのコマンドに "spam" を追加するなど) を行うでしょう。
この PySpam_System() 関数を、ほかの拡張モジュールにも公開します。
PySpam_System() は普通の C 関数で、ほかと同じく static として宣言します。
static int
PySpam_System(const char *command)
{
return system(command);
}
spam_system() には、ごく簡単な変更を加えます。
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = PySpam_System(command);
return PyLong_FromLong(sts);
}
モジュールの先頭にある以下の行
#include <Python.h>
の直後に、以下の二行を必ず追加してください:
#define SPAM_MODULE
#include "spammodule.h"
この #define は、ヘッダーファイルがクライアントモジュールではなく API を公開するモジュールからインクルードされていることを伝えます。
最後に、モジュールの mod_exec 関数で C API ポインター配列を初期化する必要があります。
static int
spam_module_exec(PyObject *m)
{
static void *PySpam_API[PySpam_API_pointers];
PyObject *c_api_object;
/* C API ポインターの配列を初期化する */
PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;
/* API ポインター配列のアドレスを格納した Capsule を作成する */
c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);
if (PyModule_Add(m, "_C_API", c_api_object) < 0) {
return -1;
}
return 0;
}
Note that PySpam_API is declared static; otherwise the pointer
array would disappear when PyInit_spam() terminates!
からくりの大部分はヘッダファイル spammodule.h 内にあり、以下のようになっています:
#ifndef Py_SPAMMODULE_H
#define Py_SPAMMODULE_H
#ifdef __cplusplus
extern "C" {
#endif
/* spammodule のヘッダーファイル */
/* C API 関数 */
#define PySpam_System_NUM 0
#define PySpam_System_RETURN int
#define PySpam_System_PROTO (const char *command)
/* C API ポインターの総数 */
#define PySpam_API_pointers 1
#ifdef SPAM_MODULE
/* この部分は spammodule.c のコンパイル時に使う */
static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;
#else
/* この部分は spammodule の API を使うモジュールで使う */
static void **PySpam_API;
#define PySpam_System \
(*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])
/* エラー時は -1、成功時は 0 を返す。
* エラーがあれば PyCapsule_Import が例外を設定する。
*/
static int
import_spam(void)
{
PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);
return (PySpam_API != NULL) ? 0 : -1;
}
#endif
#ifdef __cplusplus
}
#endif
#endif /* !defined(Py_SPAMMODULE_H) */
All that a client module must do in order to have access to the function
PySpam_System() is to call the function (or rather macro)
import_spam() in its mod_exec function:
static int
client_module_exec(PyObject *m)
{
if (import_spam() < 0) {
return -1;
}
/* 必要ならここで追加の初期化を行う */
return 0;
}
この方法の主な欠点は、spammodule.h がやや複雑になることです。
ただし、公開するどの関数でも基本構造は同じなので、一度覚えれば済みます。
最後に、Capsule には追加の機能もあり、特に格納するポインターに関わるメモリの確保や解放に役立ちます。
詳細は、Python/C API リファレンスマニュアルの カプセル と、Capsule の実装 (Python のソース配布物にある Include/pycapsule.h と Objects/capsule.c) を参照してください。
脚注