#
2.1 基本的なこと

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()` が呼ばれたときに どれくらいの量のメモリを割り当てればよいのか知ることができます。

        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 型はまだおもしろみに欠けています。 何もデータを持ってないし、何もしてはくれません。 継承してサブクラスを作ることさえできないのです。