1.4 モジュールのメソッドテーブルと初期化関数

さて、前に約束したように、spam_system() Python プログラム からどうやって呼び出すかをこれから示します。まずは、関数名とアドレスを ``メソッドテーブル (method table)'' に列挙する必要があります:

static PyMethodDef SpamMethods[] = {
    ...
    {"system",  spam_system, METH_VARARGS,
     "Execute a shell command."},
    ...
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

リスト要素の三つ目のエントリ ("METH_VARARGS") に注意してください。 このエントリは、C 関数が使う呼び出し規約をインタプリタに教えるための フラグです。通常この値は"METH_VARARGS" か "METH_VARARGS | METH_KEYWORDS" のはずです; 0 は旧式のPyArg_ParseTuple() の変化形が使われることを 意味します。

"METH_VARARGS" だけを使う場合、C 関数は、Python レベルでの引数が PyArg_ParseTuple() が受理できるタプルの形式で渡されるもの と想定しなければなりません; この関数についての詳細は下で説明します。

関数にキーワード引数が渡されることになっているのなら、 第三フィールドにMETH_KEYWORDS ビットをセットできます。 この場合、C 関数は第三引数に "PyObject *" を受理するように せねばなりません。このオブジェクトは、キーワード引数の辞書に なります。こうした関数で引数を解釈するには、 PyArg_ParseTupleAndKeywords() を使ってください。

メソッドテーブルは、モジュールの初期化関数内でインタプリタに 渡さねばなりません。初期化関数はモジュールの名前を name としたときに initname() という名前でなければ ならず、モジュールファイル内で定義されているもののうち、唯一の 非static 要素でなければなりません:

PyMODINIT_FUNC
initspam(void)
{
    (void) Py_InitModule("spam", SpamMethods);
}

PyMODINIT_FUNC は関数の戻り値を void になるように宣言し、 プラットフォーム毎に必要とされる、特有のリンク宣言 (linkage declaration) を定義すること、さらに C++ の場合には関数を extern "C" に 宣言することに注意してください。

Python プログラムがモジュール spam を初めて import するとき、initspam() が呼び出されます。 (Python の埋め込みに関するコメントは下記を参照してください。) initspam()Py_InitModule() を呼び出して ``モジュールオブジェクト'' を生成し (オブジェクトは"spam" を キーとして辞書 sys.modules に挿入されます)、第二引数として 与えたメソッドテーブル (PyMethodDef 構造体の配列) の情報に 基づいて、組み込み関数オブジェクトを新たなモジュールに挿入していきます。 Py_InitModule() は、自らが生成した (この段階ではまだ未使用の) モジュールオブジェクトへのポインタを返します。 Py_InitModule() は、モジュールを満足に初期化できなかった場合、 致命的エラーで中断するため、この関数の呼び出し側がエラーをチェックする 必要はありません。

Python を埋め込む場合には、_PyImport_Inittab テーブルの エントリ内に initspam() がない限り、initspam() は自動的には呼び出されません。この問題を解決する最も簡単な方法は、 Py_Initialize()PyMac_Initialize() を 呼び出した後に initspam() を直接呼び出し、 静的にリンクしておいたモジュールを静的に初期化してしまうというものです:

int
main(int argc, char *argv[])
{
    /* Python インタプリタに argv[0] を渡す */
    Py_SetProgramName(argv[0]);

    /* Python インタプリタを初期化する。必ず必要。 */
    Py_Initialize();

    /* 静的モジュールを追加する */
    initspam();

Python ソース配布物中の Demo/embed/demo.c ファイル内 に例があります。

注意: 単一のプロセス内 (または fork() 後の exec() が介入していない状態) における複数のインタプリタにおいて、 sys.module から エントリを除去したり新たなコンパイル済みモジュールを import したりすると、拡張モジュールによっては問題を生じることがあります。 拡張モジュールの作者は、内部データ構造を初期化する際にはよくよく 用心すべきです。また、reload() 関数を拡張モジュールに 対して利用でき、この場合はモジュール初期化関数 (initspam()) は呼び出されますが、モジュールが動的にロード可能なオブジェクトファイル (Unixでは .so、Windows では .dll) から読み出された 場合にはモジュールファイルを再読み込みしないので注意してください。

より実質的なモジュール例は、Python ソース配布物に Modules/xxmodule.c という名前で入っています。 このファイルはテンプレートとしても利用できますし、単に例としても 読めます。ソース配布物や Windows にインストールされた Python に入っている modulator.py では、拡張モジュールで実装しなければならない 関数やオブジェクトを宣言し、実装部分を埋めて作成するためのテンプレート を生成できるような、簡単なグラフィカルユーザインタフェースを 提供しています。 このスクリプトはTools/modulator/ ディレクトリにあります; 詳しくはディレクトリ内の README ファイルを参照してください。

ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。