| ■ | Python Extensionのつくりかた |
| ■ | アーキテクチャ |
| ■ | Tcl/Tkの拡張との違い |
| ■ | サンプルプログラム |
import sample result = sample.add2(15, 30) print result |
とすると、「45」が出力されるという、 要するに2つの引数の和を返す関数を提供するモジュールです。
/* samplemodule.c */
#include "Python.h"
static PyObject* sample_add2(PyObject* self, PyObject* args){
long ope1, ope2;
if(! PyArg_ParseTuple(args, "ll", & ope1, & ope2))
return NULL;
return Py_BuildValue("l", ope1 + ope2);
}
static PyMethodDef MethodsDefs[] = {
{"add2", sample_add2, METH_VARARGS},
{NULL, NULL, 0},
};
void initsample(void){
(void) Py_InitModule("sample", MethodsDefs);
}
/* end. */
|
非常に短いですが、書く必要のあるソースプログラムはとりあえずこれだけです。 私自身の整理のためも含めて確認しますと、 このソースプログラム「samplemodule.c」は、 Python言語のモジュール「sample」を含む共有ライブラリ「samplemodule.so」 のソースプログラムです。
で、順に説明すると、まず1行目
#include "Python.h"このように、Pythonの拡張モジュールでは "Python.h" をインクルードするだけでOKです。Python.hはPython処理系をインストールしたときに、 ヘッダファイルのパス(例えば /usr/local/include/python1.6 )に置かれます。
static PyObject* sample_add2(PyObject* self, PyObject* args){
long ope1, ope2;
if(! PyArg_ParseTuple(args, "ll", & ope1, & ope2))
return NULL;
return Py_BuildValue("l", ope1 + ope2);
}
Pythonの1つのメソッドを定義するのは、このような形をした1つのC言語の関数です。
プロトタイプは必ずこの形で毎回使い回しが効きます。
関数名に特に制限はありませんが、上でやっているように、
「モジュール名+_+メソッド名」と名付けると分かりやすいでしょう。
もちろん、個人の好みで自由です。
そして肝心の処理ですが、上の例が最も基本的な、いわば「定石」でしょうか?つまり、
sample.add2(15, 30)とすると、上のope1に15、ope2に30が格納されるので、後はこれらを使って処理をします。「l」の他にはint変数を表す「i」、倍精度浮動小数点数を表す「d」、 文字列を表す「s」など、たくさんの種類があります。 これはPythonのマニュアル「Extending and Embedding」に一覧が載っていますが、 大抵はこの「i」「l」「d」「s」で用が足せるはずです。 PyArg_ParseTupleは、指定した型の引数がPython側でちゃんと渡されなかったときはエラーとしてNULLを返してくるので、 この場合はNULLを返せばPython処理系が自動的にエラーを上げてくれます。
static PyMethodDef MethodsDefs[] = {
{"add2", sample_add2, METH_VARARGS},
{NULL, NULL, 0},
};
void initsample(void){ /* 初期化関数 */
(void) Py_InitModule("sample", MethodsDefs);
}
この部分に関しては、何をしているのか、大体想像がつくと思うので、
詳しい紹介は省きますね。ですが、2点だけ、
記号定数METH_VARARGSというのは、
最初はここに使うもんだということで決め打ちで使い回せばよいでしょう。
もう1点、初期化関数の名前ですが、これは
「「init」+モジュール名」
で固定で、変更することはできません。
ここではPy_InitModuleで指定するモジュール名が
"sample" なので、
必ず initsample としなくてはいけません。
| ■ | コンパイルしてみよう |
Pythonモジュールを共有ライブラリやDLLとしてコンパイルする方法は、 プラットフォームによって微妙に異なりますが、 それらを意識せずにモジュールを構築できるように、 Python本体処理系で提供されているツールを使ってみましょう。 Python本体処理系のソースアーカイブを展開し、 Misc/Makefile.pre.in というファイルをコピーして持って来ます。 このファイルには一切修正、変更は不要なので、とりあえず持って来て、 適当に置いておきます。
まず、上のようにしてソースプログラム、ここではsamplemodule.cを書きます。 で、次に下のような簡単なテキストファイルを作り、 「Setup」という名前で保存します。大文字小文字も含めて、必ずこの名前にします。
*shared* sample samplemodule.o # end. |
1行目は、ビルドしているプラットフォームが共有ライブラリのランタイムロードをサポートしていることを宣言するもので、 現在大抵のUNIXがこの *shared* でいいと思います。 ちなみに #で始まる行はコメントです。
LUNCH=/usr/urano/lunch ura uramodule.c -I$(LUNCH) $(LUNCH)/liblunch.aこのように、Setupの中にはMakefileと同じ形でマクロ定義を行うことも可能です。 ソースプログラム、Makefile.pre.in、Setupがそろったら、 シェルのコマンドラインから次のように打ちます。
% make -f Makefile.pre.in boot |
% make |
samplemodule.soができれば完了です。 出来上がる共有ライブラリの名前は選べません。 「sample」モジュールの場合は「samplemodule.so」 (またはその後ろにバージョン番号がずらずらついたもの)に固定です。 あとは修正・改良するにはソースプログラムを編集してmakeの繰り返しでOKです。
Cコンパイラのスイッチの知識がある場合は、 直接このようにしてコンパイルしてもよいでしょう。
% gcc -c -fPIC -I/usr/local/include/python1.6 sample.c % gcc -shared -o samplemodule.so sample.o |
ただしこれはLinux上のgccコンパイラの例です。 ccコンパイラを使う場合には、 プラットフォームによって共有ライブラリをビルドするためのスイッチが全く違うため、 このスイッチを正確に知らない場合には、 面倒を避けるために上で紹介した方法を使った方がよいでしょう。
| ■ | 使ってみよう |
% python Python 1.6a2(...) Copyright 1991-1995 ... >>>import sample >>>sample.add2(10,20) 30 ^D |
| ■ | さらに機能を追加してみる |
r = sample.monthname(10) |
とするとrに「Oct」が返ってくるという、そんな感じのモジュールです。じゃ、
r = sample.monthname(13) |
とするとどーなるの?つまり、 エラー処理をどうやって書くかというのがこのサンプルの趣旨というわけです。
#include "Python.h"
/* add2(param1, param2) */
static PyObject* sample_add2(PyObject* self, PyObject* args){
long ope1, ope2;
if(! PyArg_ParseTuple(args, "ll", & ope1, & ope2))
return NULL;
return Py_BuildValue("l", ope1 + ope2);
}
/* monthname(month) */
static PyObject* sample_monthname(PyObject* self, PyObject* args){
int m;
static char* monthnames[] =
{"", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
if(! PyArg_ParseTuple(args, "i", & m)) return NULL;
if(m < 1 || m > 12){
PyErr_SetString(PyExc_RuntimeError, "month must be in 1..12");
return NULL;
}
return Py_BuildValue("s", monthnames[m]);
}
static PyMethodDef MethodsDefs[] = {
{"add2", sample_add2, METH_VARARGS},
{"monthname", sample_monthname, METH_VARARGS},
{NULL, NULL, 0},
};
void initsample(void){
(void) Py_InitModule("sample", MethodsDefs);
}
/* end. */
|
見てそのままだと思うので説明はいらないと思いますが、 説明しちゃいますね(どっちやねん) えー両者和解案として、ちょっとだけ説明するというのはどうでしょう(帰れ!) じゃ、ほんとにちょっとだけ。 「これはエラーになるべきにゃ」という状況になったら、 PyErr_SetStringでエラーメッセージをセットして、 NULLを返すというのがごくごく普通のエラー処理になります。 記号定数PyExc_RuntimeErrorというのは、 ありていに言って「どうともとれる優柔不断なエラーコード」 という意味で、面倒なら毎回これを使えば楽でよいです(ほんとかなあ) もっと状況を細かく説明するエラーの記号定数はマニュアルに一覧が載っているので、 そちらを当ってみてくださいね。
if(m < 1 || m > 12){
PyErr_SetString(PyExc_RuntimeError, "month must be in 1..12");
return NULL;
}
セクションのサブメニューに戻る
(first uploaded 2000/08/26 last updated 2002/07/20)