[PR]今日のニュースは
「Infoseek モバイル」


Python Extensionのつくりかた

 Pythonの本体処理系にはデータ処理、 テキスト処理などいろいろ便利な機能を提供するモジュール(Module)が大量に付属してきます。 それに加え、 UNIXプラットフォーム向けを中心にフリーで配布されるモジュールも数多く、 それらだけでも十分なプログラムを作ることができます。 しかし、C言語を使えば、 比較的簡単に独自の機能を持ったモジュール(Python Extension)を自作することもできます。 ここでは、UNIX上のPython 1.5以降で、簡単な自作モジュール「sample」 を作った経過をメモっています。 実際に使ったプラットフォームは次の通りです。


アーキテクチャ

 Python Extensionは、論理的にはモジュールなわけですが、 物理的にいうと、UNIX上では共有ライブラリ、 MS-Windows上ではダイナミックリンクライブラリ(DLL)です。 1つの共有ライブラリ内をビルドすることで1つのモジュールを作るのがわかりやすいので、 ここではこの例にすることにします。


Tcl/Tkの拡張との違い

 Tcl/Tkの場合は、独自の機能をもった「コマンド」を作ることができます。 Tcl/Tkでは、制御文もなんでも全部「コマンド」 なので、制御文など基本文法そのものを変えるようなコマンドも自作できます。 一方、Pythonの場合はあくまで自分オリジナルのモジュール (と、その中に含まれる関数)を作れるというだけなので、 Pythonの基本文法を改変するような、制御構造を追加したりということはできません。


サンプルプログラム

 では、非常に簡単なモジュールを、 そうですね、「sample」という名前のモジュールを作ることにしましょう。 sampleには「sample.add2」という関数が1個だけあって、

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言語の関数です。 プロトタイプは必ずこの形で毎回使い回しが効きます。 関数名に特に制限はありませんが、上でやっているように、 「モジュール名+_+メソッド名」と名付けると分かりやすいでしょう。 もちろん、個人の好みで自由です。

 そして肝心の処理ですが、上の例が最も基本的な、いわば「定石」でしょうか?つまり、

  1. まずPyArg_ParseTupleを使って、 Python世界のメソッドの引数をC言語世界の変数に切り出します。
  2. 次にそれらを元に処理をして、返す値をPy_BuildValue を使ってC言語世界のPyObject構造体にし、 これをPythonオブジェクトとしてPython世界に戻します。
橋を渡るのに、吊り橋の工法を知る必要がないのと同様、 Python世界とC言語世界がどのような架け橋で連絡されているのか、 あまり詳しく知る必要はないでしょう。 橋の両側でうまく車を橋に載せてやればいいということですね! というわけで簡単にふれると、 PyArg_ParseTupleは可変個の引数を取るAPI関数で、 最初の引数はargsで固定です。 2番目の引数が分解すべきメソッドの引数の「型」と数を指定するもので、 ここの「l」というのは、「long型=4バイトの整数」の引数という意味で、 「ll」だとそれが2個、という意味です。例えば、
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処理系が自動的にエラーを上げてくれます。
 Py_BuildValueの最初の引数は、 今度はPythonに返す戻り値の型をやっぱり「l」「s」などで指定します。 で、2番目の引数で実際にその型をした値を渡せばOKです。
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モジュールを作るには、Python処理系がインストールされている必要があり、 またC言語のコンパイラが必要です。

 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

うまくいけば、「Makefile」が生成されます。 今度は、そのMakefileを使ってコンパイルします。

% 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コンパイラを使う場合には、 プラットフォームによって共有ライブラリをビルドするためのスイッチが全く違うため、 このスイッチを正確に知らない場合には、 面倒を避けるために上で紹介した方法を使った方がよいでしょう。


使ってみよう

 完成した共有ライブラリは環境変数PYTHONPATHの通っている場所に置いておきます。 実行は簡単。

% python
Python 1.6a2(...)
Copyright 1991-1995 ...
>>>import sample
>>>sample.add2(10,20)
30
^D


さらに機能を追加してみる

 今度は同じ「sample」モジュールに、もうひとつ「monthname」 というメソッドを追加してみます。これは、

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)