PyCodeObjectを書き換える [Main] PythonのバイトコードをGRINEditで可視化

PyCodeObjectを書き換える[C(2)]

PyCodeObjectを書き換える[Python(18)]PythonのバイトコードをGRINEditで可視化

Python拡張の作り方

いくつかの落とし穴をよければ意外と簡単だったので、 この文章を必要としている誰かのために整理してみることにします。

追記: おびなたのはてな日記 - distutils with boost.pythonで詳しく説明されているように、C++とBoostを使うともっと楽になるようです。近いうちに試してみます。 また、 Cybozu Developer Network: Python調査報告 (2006/10) によれば 「PEAKの setuptools は distutils の拡張で(中略)distutils の上位互換であるため、setuptools を使用しない理由はありません。積極的に使用しましょう。」とのことなのでこちらも試してみたいと思います。

Cのコードを用意する

書き換えるべき所を明確にするために、 [[MODULE_NAME]]というように表記しました。 同じ名前の括弧には同じ文字列が入らなければいけません。 ***と書いてある部分には、文脈にあわせて適切なことを書く必要があります。
#include <Python.h>

static PyObject *
[[FUNC_NAME]](PyObject *self, PyObject *args)
{
  ***; // 変数の宣言

  if (!PyArg_ParseTuple(args, ***)) //受け取る引数にあわせて書き換える
        return NULL;

  ***; // 肝心の処理

  Py_RETURN_NONE; //値を返さないでいい場合
  return Py_BuildValue(***); //返す場合
}

static PyMethodDef Methods[] = {
  {"[[PYTHON_FUNC_NAME]]", [[FUNC_NAME]], METH_VARARGS, "[[関数の説明]]"},
  {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
init[[MODULE_NAME]](void)
{
  (void) Py_InitModule("[[MODULE_NAME]]", Methods);
} 

setup.pyを作る

from distutils.core import setup, Extension

module1 = Extension(
	'[[PACKAGE_NAME]].[[MODULE_NAME]]',
	sources = ['[[C_FILE_NAME]].c']
)

setup(
	name = '[[PACKAGE_NAME]]',
	version = '[[バージョン]]',
	description = '[[説明文]]',
	url = '[[URL]]',
	author = '[[NAME]]',
	author_email = '[[E-MAIL]]',
	ext_modules = [module1],
	packages=["[[PACKAGE_NAME]]"],
)
PACKAGE_NAMEはPYTHON_MODULE_NAMEと同じでもかまいません。 Extensionの引数に「A.B」と指定した場合、 Aというフォルダの中にB.pydが生成されます。

パッケージを作る

[[PACKAGE_NAME]]と同じ名前のフォルダを作り、 そこに__init__.pyやテスト用のスクリプトなどを置きます。 この位置に置いたスクリプトは[[MODULE_NAME]].pydと同じフォルダにコピーされるので import [[MODULE_NAME]]でインポートすることができます。

パッケージを作るようにsetup.pyを記述したので、 ここで__init__.pyを作成して最低限下のように書いておかないと この後のリリースの時に困ります。

import [[MODULE_NAME]]

__all__ = ["[[MODULE_NAME]]"]
__init__.pyを作る手間が惜しければパッケージを指定しないで、 作成されたバイナリを手でパスの通ったところにコピーしても構いませんが、 あとで余計に手間がかかるかもしれません。

ビルド

コマンドプロンプトを立ち上げてsetup.pyのあるフォルダへ移動します。 基本的には「python setup.py build」でdistutilsがよしなに取りはからってくれます。 筆者の環境では「Cygwinでビルドするなら-c mingw32とつけろ」とメッセージが出るので 「python setup.py build -c mingw32」としました。

Cのコードに問題がある場合はここで表示されます。

テスト

buildフォルダの中(たとえばbuild\lib.win32-2.5\[[PACKAGE_NAME]])に コンパイルされたバイナリと、その他のスクリプトがコピーされているはずです。 テスト用のスクリプトを起動して動作確認をします。

コンパイルされたモジュールを一度インポートすると、 そのインタプリタが生きている間は上書きに失敗するためコンパイルができません。 delでモジュールを消しても同様です。 Cのコードを書き換えてビルドし直す場合には忘れずにインタプリタを終了しましょう。

インストーラの作成

「python setup.py bdist_wininst」でウィンドウズ版のインストーラが作成されます。 しかし筆者の環境では「-c mingw32をつけろ」というエラーメッセージがが表示され、 言われたとおりにしてもエラーになります。 「python setup.py bdist_wininst --skip-build」 とやってビルドのプロセスを飛ばすのが正解です。

ソースリリースの作成

Windows用のインストーラだけでは他のOSを使っている人が困るので、 ソースリリースも作成します。 「python setup.py sdist」でOK、のはずですがなぜか パッケージの中身の__init__.pyやtest.pyがコピーされなかったので手でコピーしました。 Windows用インストーラの方はきちんとコピーされているので謎です。

まとめ

何年か前にPythonの拡張ライブラリを作ろうとしたときには gccでにどういうコンパイルオプションをつければPython.hへのパスが通るのか… などと悩んだのですが、 distutilsを使うことでこんなに簡単にインストーラまでできてしまうことに驚きました。 ぜひ一度試してみることをおすすめします。

トラックバック(Trackback)

この一覧は、次のエントリーを参照しています: Python拡張の作り方:

» [技]boost.python from おびなたのはてな日記
西尾泰和のブログ @ Cybozu Labs: Python拡張の作り方 それ、C++ならBoost.Python使ってもっと簡単にできるお。 Boos...

おお、試してみて「続・Python拡張の作り方」を書きます!ありがとう!
[詳しくはこちら]