Python ランタイムでは、すべての Python オブジェクトは
PyObject* 型の変数として扱います。PyObject は
さほど大仰なオブジェクトではなく、単にオブジェクトに対する参照回数と、
そのオブジェクトの「タイプオブジェクト (type object)」へのポインタを
格納しているだけです。
重要な役割を果たしているのはこのタイプオブジェクトです。
つまりタイプオブジェクトは、例えばあるオブジェクトのある属性が参照される
とか、あるいは別のオブジェクトとの間で乗算を行うといったときに、
どの (C の) 関数を呼び出すかを決定しているのです。
これらの C 関数は「タイプメソッド (type method)」と呼ばれ、
[].append
のようなもの
(いわゆる「オブジェクトメソッド (object method)」) とは区別しています。
なので、新しいオブジェクトの型を定義したいときは、 新しいタイプオブジェクトを作成すればよいわけです。
この手のことは例を見たほうが早いでしょうから、 ここに最小限の、しかし完全な、新しい型を定義するモジュールをあげておきます:
#include <Python.h> typedef struct { PyObject_HEAD /* Type-specific fields go here. */ } noddy_NoddyObject; static PyTypeObject noddy_NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "noddy.Noddy", /*tp_name*/ sizeof(noddy_NoddyObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ 0, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "Noddy objects", /* tp_doc */ }; static PyMethodDef noddy_methods[] = { {NULL} /* Sentinel */ }; #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ #define PyMODINIT_FUNC void #endif PyMODINIT_FUNC initnoddy(void) { PyObject* m; noddy_NoddyType.tp_new = PyType_GenericNew; if (PyType_Ready(&noddy_NoddyType) < 0) return; m = Py_InitModule3("noddy", noddy_methods, "Example module that creates an extension type."); Py_INCREF(&noddy_NoddyType); PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType); }
さしあたって覚えておくことは以上ですが、これで前の章からすこしは 説明がわかりやすくなっていることと思います。
最初に習うのは、つぎのようなものです:
typedef struct { PyObject_HEAD } noddy_NoddyObject;
これが Noddy オブジェクトの内容です -- このケースでは、
ほかの Python オブジェクトが持っているものと何ら変わりはありません。
つまり参照カウントと型オブジェクトへのポインタですね。これらは
PyObject_HEAD
マクロによって
展開されるメンバです。マクロを使う理由は、レイアウトを標準化するためと、
デバッグ用ビルド時に特別なデバッグ用のメンバを定義できるようにするためです。
この PyObject_HEAD
マクロの後にはセミコロンがないことに
注意してください。
セミコロンはすでにマクロ内に含まれています。うっかり後にセミコロンを
つけてしまわないように気をつけて。
これはお使いの機種では何の問題も起こらないかもしれませんが、
機種によっては、おそらく問題になるのです!
(Windows 上では、MS Visual C がこの手のエラーを出し、
コンパイルできないことが知られています)
比較のため、以下に標準的な Python の整数型の定義を見てみましょう:
typedef struct { PyObject_HEAD long ob_ival; } PyIntObject;
では次にいってみます。かなめの部分、タイプオブジェクトです。
static PyTypeObject noddy_NoddyType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "noddy.Noddy", /*tp_name*/ sizeof(noddy_NoddyObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ 0, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "Noddy objects", /* tp_doc */ };
object.h の中にある PyTypeObject の定義を見ると、 実際にはここに挙げた以上の数のメンバがあるとわかるでしょう。 これ以外のメンバは C コンパイラによってゼロに初期化されるので、 必要な時を除いてふつうはそれらの値を明示的には指定せずにおきます。
次のものは非常に重要なので、とくに最初の最初に見ておきましょう:
PyObject_HEAD_INIT(NULL)
これはちょっとぶっきらぼうですね。実際に書きたかったのはこうです:
PyObject_HEAD_INIT(&PyType_Type)
この場合、タイプオブジェクトの型は「type」という名前になりますが、 これは厳密には C の基準に従っておらず、コンパイラによっては文句を 言われます。 幸いにも、このメンバは PyType_Ready() が埋めてくれます。
0, /* ob_size */
ヘッダ中の ob_size メンバは使われていません。 これは歴史的な遺物であり、構造体中にこれが存在しているのは 古いバージョンの Python 用にコンパイルされた拡張モジュールとの バイナリ上の互換性を保つためです。ここにはつねにゼロを指定してください。
"noddy.Noddy", /* tp_name */
これは型の名前です。この名前はオブジェクトのデフォルトの表現形式と、 いくつかのエラーメッセージ中で使われます。たとえば:
>>> "" + noddy.new_noddy() Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: cannot add type "noddy.Noddy" to string
注意: この名前はドットで区切られた名前で、モジュール名と、そのモジュール内での 型名を両方ふくんでいます。この場合のモジュールは noddy で、型の名前は Noddy ですから、ここでの型名としては noddy.Noddy を 指定するわけです。
sizeof(noddy_NoddyObject), /* tp_basicsize */
これによって Python は PyObject_New() が呼ばれたときに どれくらいの量のメモリを割り当てればよいのか知ることができます。
注意: あなたのタイプを Python でサブクラス化可能にしたい場合、 そのタイプが基底タイプと同じ tp_basicsize をもっていると 多重継承のときに問題が生じることがあります。そのタイプを Python の サブクラスにしたとき、その __bases__ リストにはあなたの タイプが最初にくるようにしなければなりません。さもないとエラーの発生なしに あなたのタイプの __new__ メソッドを呼び出すことはできなくなります。 この問題を回避するには、つねにあなたのタイプの tp_basicsize を その基底タイプよりも大きくしておくことです。ほとんどの場合、 あなたのタイプは object か、そうでなければ基底タイプにデータ用の メンバを追加したものでしょうから、したがって大きさはつねに増加するため この条件は満たされています。
0, /* tp_itemsize */
これはリストや文字列などの可変長オブジェクトのためのものです。 今のところ無視しましょう。
このあとのいくつかのメソッドは使わないのでとばして、 クラスのフラグ (flags) には Py_TPFLAGS_DEFAULT を入れます。
Py_TPFLAGS_DEFAULT, /*tp_flags*/
すべての型はフラグにこの定数を含めておく必要があります。 これは現在のバージョンの Python で定義されているすべての メンバを許可します。
この型の docstring は tp_doc に入れます。
"Noddy objects", /* tp_doc */
ここからタイプメソッドに入るわけですが。 ここがあなたのオブジェクトが他と違うところです。 でも今回のバージョンでは、これらはどれも実装しないでおき、 あとでこの例をより面白いものに改造することにしましょう。
とりあえずやりたいのは、この Noddy オブジェクトを新しく 作れるようにすることです。オブジェクトの作成を許可するには、 tp_new の実装を提供する必要があります。今回は、 API 関数によって提供されるデフォルトの実装 PyType_GenericNew() を 使うだけにしましょう。これを単に tp_new スロットに代入すれば よいのですが、 これは互換上の理由からできません。プラットフォームやコンパイラによっては、 構造体メンバの初期化に別の場所で定義されている C の関数を代入することは できないのです。 なので、この tp_new の値はモジュール初期化用の関数で代入します。 PyType_Ready() を呼ぶ直前です:
noddy_NoddyType.tp_new = PyType_GenericNew; if (PyType_Ready(&noddy_NoddyType) < 0) return;
これ以外のタイプメソッドはすべて NULLです。 これらについては後ほどふれます。
このファイル中にある他のものは、どれもおなじみでしょう。 initnoddy() のこれを除いて:
if (PyType_Ready(&noddy_NoddyType) < 0) return;
この関数は、上で NULLに指定していた ob_type などの いくつものメンバを埋めて、Noddy 型を初期化します。
PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
これはこの型をモジュール中の辞書に埋め込みます。これで、 Noddy クラスを呼べば Noddy インスタンスを作れるように なりました:
>>> import noddy >>> mynoddy = noddy.Noddy()
これだけです! 残るはこれをどうやってビルドするかということです。 上のコードを noddy.c というファイルに入れて、 以下のものを setup.py というファイルに入れましょう。
from distutils.core import setup, Extension setup(name="noddy", version="1.0", ext_modules=[Extension("noddy", ["noddy.c"])])
そして、シェルから以下のように入力します。
$ python setup.py build
これでサブディレクトリの下にファイル noddy.so が作成されます。
このディレクトリに移動して Python を起動しましょう。
import noddy
して Noddy オブジェクトで遊べるようになっているはずです。
そんなにむずかしくありません、よね?
もちろん、現在の Noddy 型はまだおもしろみに欠けています。 何もデータを持ってないし、何もしてはくれません。 継承してサブクラスを作ることさえできないのです。