2. 新しい型を定義する

前の章でふれたように、Python では拡張モジュールを書くプログラマが Python のコードから操作できる、新しい型を定義できるようになっています。ちょうど Python の中核にある文字列やリストをつくれるようなものです。

これはそんなにむずかしくはありません。拡張型のためのコードにはすべて、一定のパターンが存在しています。しかし始める前に、いくつか細かいことを理解しておく必要があるでしょう。

注釈

Python 2.2 から、新しい型を定義する方法がかなり変わって (良くなって) います。この文書は Python 2.2 およびそれ以降で新しい型をどうやって定義するかについて述べています。古いバージョンの Python をサポートする必要がある場合は、 この文書の古い版 を参照してください。

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 = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "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 = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "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 コンパイラによってゼロに初期化されるので、必要な時を除いてふつうはそれらの値を明示的には指定せずにおきます。

次のものは非常に重要なので、とくに最初の最初に見ておきましょう:

PyVarObject_HEAD_INIT(NULL, 0)

これはちょっとぶっきらぼうですね。実際に書きたかったのはこうです:

PyVarObject_HEAD_INIT(&PyType_Type, 0)

この場合、タイプオブジェクトの型は「type」という名前になりますが、これは厳密には C の基準に従っておらず、コンパイラによっては文句を言われます。幸いにも、このメンバは PyType_Ready() が埋めてくれます。

"noddy.Noddy",              /* tp_name */

これは型の名前です。この名前はオブジェクトのデフォルトの表現形式と、いくつかのエラーメッセージ中で使われます。たとえば:

>>> "" + noddy.new_noddy()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot add type "noddy.Noddy" to string

注意: この名前はドットで区切られた名前で、モジュール名とそのモジュール内での型名の両方を含んでいます。 この場合のモジュールは noddy で型は Noddy ですから、ここでの型名としては noddy.Noddy を指定するわけです。 ドットで区切られていない名前を使うと、文書ツールの pydoc がその新しい型をモジュールの文書に載せなくなるという副作用があります。

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

2.1.1. 基本のサンプルにデータとメソッドを追加する

この基本のサンプルにデータとメソッドを追加してみましょう。ついでに、この型を基底クラスとしても利用できるようにします。ここでは新しいモジュール noddy2 をつくり、以下の機能を追加します:

#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} Noddy;

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }

    return 0;
}


static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* 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 |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Noddy objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy2(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy2", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

このバージョンでは、いくつもの変更をおこないます。

以下の include を追加します:

#include <structmember.h>

すこしあとでふれますが、この include には属性を扱うための宣言が入っています。

Noddy オブジェクトの構造体の名前は Noddy に縮めることにします。タイプオブジェクト名は NoddyType に縮めます。

これから Noddy 型は 3つのデータ属性をもつようになります。 firstlast 、および number です。 firstlast 属性はファーストネームとラストネームを格納した Python 文字列で、 number 属性は整数の値です。

これにしたがうと、オブジェクトの構造体は次のようになります:

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

いまや管理すべきデータができたので、オブジェクトの割り当てと解放に際してはより慎重になる必要があります。最低限、オブジェクトの解放メソッドが必要です:

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

この関数は tp_dealloc メンバに代入されます。

(destructor)Noddy_dealloc, /*tp_dealloc*/

このメソッドでやっているのは、ふたつの Python 属性の参照カウントを減らすことです。 first メンバと last メンバが NULL かもしれないため、ここでは Py_XDECREF() を使いました。このあとそのオブジェクトのタイプメソッドである tp_free メンバを呼び出しています。ここではオブジェクトの型が NoddyType とは限らないことに注意してください。なぜなら、このオブジェクトはサブクラス化したインスタンスかもしれないからです。

ファーストネームとラストネームを空文字列に初期化しておきたいので、新しいメソッドを追加することにしましょう:

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL)
          {
            Py_DECREF(self);
            return NULL;
          }

        self->last = PyString_FromString("");
        if (self->last == NULL)
          {
            Py_DECREF(self);
            return NULL;
          }

        self->number = 0;
    }

    return (PyObject *)self;
}

そしてこれを tp_new メンバとしてインストールします:

Noddy_new,                 /* tp_new */

この新しいメンバはその型のオブジェクトを (初期化するのではなく) 作成する責任を負っています。Python ではこのメンバは __new__() メソッドとして見えています。 __new__() メソッドについての詳しい議論は "Unifying types and classes in Python" という題名の論文を見てください。 new メソッドを実装する理由のひとつは、インスタンス変数の初期値を保証するためです。この例でやりたいのは new メソッドが first メンバと last メンバの値を NULL でないようにするということです。もしこれらの初期値が NULL でもよいのであれば、先の例でやったように、new メソッドとして PyType_GenericNew() を使うこともできたでしょう。 PyType_GenericNew() はすべてのインスタンス変数のメンバを NULL にします。

この new メソッドは静的なメソッドで、インスタンスを生成するときにその型と、型が呼び出されたときの引数が渡され、新しいオブジェクトを作成して返します。new メソッドはつねに、あらかじめ固定引数 (positional argument) とキーワード引数を取りますが、これらのメソッドはしばしばそれらの引数は無視して初期化メソッドにそのまま渡します。new メソッドはメモリ割り当てのために tp_alloc メンバを呼び出します。 tp_alloc をこちらで初期化する必要はありません。これは PyType_Ready() が基底クラス (デフォルトでは object) をもとに埋めるものです。ほとんどの型ではデフォルトのメモリ割り当てを使っています。

注釈

もし協力的な tp_new (基底タイプの tp_new または __new__() を呼んでいるもの) を作りたいのならば、実行時のメソッド解決順序をつかってどのメソッドを呼びだすかを決定しようとしては いけません 。つねに呼び出す型を静的に決めておき、直接その tp_new を呼び出すか、あるいは type->tp_base->tp_new を経由してください。こうしないと、あなたが作成したタイプの Python サブクラスが他の Python で定義されたクラスも継承している場合にうまく動かない場合があります。 (とりわけ、そのようなサブクラスのインスタンスを TypeError を出さずに作ることが不可能になります。)

つぎに初期化用の関数を見てみましょう:

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }

    return 0;
}

これは tp_init メンバに代入されます。

(initproc)Noddy_init,         /* tp_init */

Python では、 tp_init メンバは __init__() メソッドとして見えています。このメソッドは、オブジェクトが作成されたあとに、それを初期化する目的で使われます。 new メソッドとはちがって、初期化用のメソッドは必ず呼ばれるとは限りません。初期化用のメソッドは、インスタンスの初期値を提供するのに必要な引数を受けとります。このメソッドはつねに固定引数とキーワード引数を受けとります。

初期化メソッドは複数回呼び出される可能性があります。あなたのオブジェクトの __init__() メソッドは、誰にでも呼び出すことができるからです。このため、新しい値を代入するさいには特別な注意を払う必要があります。たとえば、 first メンバには以下のように代入したくなるかもしれません:

if (first) {
    Py_XDECREF(self->first);
    Py_INCREF(first);
    self->first = first;
}

しかしこのやり方は危険です。このタイプでは first メンバに入るオブジェクトをなにも限定していないので、どんなオブジェクトでもとり得てしまうからです。それはこのコードが first メンバにアクセスしようとする前に、そのデストラクタが呼び出されてしまうかもしれないのです。このような可能性からパラノイア的に身をまもるため、ほとんどの場合メンバへの代入は,その参照カウントを減らす前におこなってください。こうする必要がないのはどんな場合でしょうか?

  • その参照カウントが 1 より大きいと確信できる場合。

  • そのオブジェクトの解放があなたのタイプのコードにコールバックするようなことが決してない場合 1

  • ガベージコレクションがサポートされていない場合に tp_dealloc ハンドラで参照カウントを減らすとき 2

ここではインスタンス変数を属性として見えるようにしたいのですが、これにはいくつもの方法があります。もっとも簡単な方法は、メンバの定義を与えることです:

static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

そして、この定義を tp_members スロットに入れましょう:

Noddy_members,             /* tp_members */

各メンバの定義はそれぞれ、メンバの名前、型、オフセット、アクセスフラグおよび docstring です。詳しくは後の "総称的な属性を管理する" (総称的な属性を管理する) の節をご覧ください。

この方法の欠点は、Python 属性に代入できるオブジェクトの型を制限する方法がないことです。ここではファーストネーム first とラストネーム last に、ともに文字列が入るよう期待していますが、今のやり方ではどんな Python オブジェクトも代入できてしまいます。加えてこの属性は削除 (del) できてしまい、その場合、 C のポインタには NULL が設定されます。たとえもしメンバが NULL 以外の値に初期化されるようにしてあったとしても、属性が削除されればメンバは NULL になってしまいます。

ここでは name() と呼ばれるメソッドを定義しましょう。これはファーストネーム first とラストネーム last を連結した文字列をそのオブジェクトの名前として返します。

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

このメソッドは C 関数として実装され、 Noddy (あるいは Noddy のサブクラス) のインスタンスを第一引数として受けとります。メソッドはつねにそのインスタンスを最初の引数として受けとらなければなりません。しばしば固定引数とキーワード引数も受けとりますが、今回はなにも必要ないので、固定引数のタプルもキーワード引数の辞書も取らないことにします。このメソッドは Python の以下のメソッドと等価です:

def name(self):
   return "%s %s" % (self.first, self.last)

first メンバと last メンバがそれぞれ NULL かどうかチェックしなければならないことに注意してください。これらは削除される可能性があり、その場合値は NULL にセットされます。この属性の削除を禁止して、そこに入れられる値を文字列に限定できればなおいいでしょう。次の節ではこれについて扱います。

さて、メソッドを定義したので、ここでメソッド定義用の配列を作成する必要があります:

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

これを tp_methods スロットに入れましょう:

Noddy_methods,             /* tp_methods */

ここでの METH_NOARGS フラグは、そのメソッドが引数を取らないことを宣言するのに使われています。

最後に、この型を基底クラスとして利用可能にしましょう。上のメソッドは注意ぶかく書かれているので、これはそのオブジェクトの型が作成されたり利用される場合についてどんな仮定も置いていません。なので、ここですべきことは Py_TPFLAGS_BASETYPE をクラス定義のフラグに加えるだけです:

Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/

initnoddy() の名前を initnoddy2() に変更し、 Py_InitModule3() に渡されるモジュール名を更新します。

さいごに setup.py ファイルを更新して新しいモジュールをビルドします:

from distutils.core import setup, Extension
setup(name="noddy", version="1.0",
      ext_modules=[
         Extension("noddy", ["noddy.c"]),
         Extension("noddy2", ["noddy2.c"]),
         ])

2.1.2. データ属性をこまかく制御する

この節では、 Noddy クラスの例にあった firstlast の各属性にたいして、より精密な制御を提供します。以前のバージョンのモジュールでは、インスタンス変数の firstlast には文字列以外のものも代入できてしまい、あまつさえ削除まで可能でした。ここではこれらの属性が必ず文字列を保持しているようにしましょう。

#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

static void
Noddy_dealloc(Noddy* self)
{
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }

    return 0;
}

static PyMemberDef Noddy_members[] = {
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_getfirst(Noddy *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }

    if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }

    Py_DECREF(self->first);
    Py_INCREF(value);
    self->first = value;

    return 0;
}

static PyObject *
Noddy_getlast(Noddy *self, void *closure)
{
    Py_INCREF(self->last);
    return self->last;
}

static int
Noddy_setlast(Noddy *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }

    if (! PyString_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }

    Py_DECREF(self->last);
    Py_INCREF(value);
    self->last = value;

    return 0;
}

static PyGetSetDef Noddy_getseters[] = {
    {"first",
     (getter)Noddy_getfirst, (setter)Noddy_setfirst,
     "first name",
     NULL},
    {"last",
     (getter)Noddy_getlast, (setter)Noddy_setlast,
     "last name",
     NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* 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 |
        Py_TPFLAGS_BASETYPE,   /* tp_flags */
    "Noddy objects",           /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    Noddy_getseters,           /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy3(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy3", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

first 属性と last 属性をよりこまかく制御するためには、カスタムメイドの getter 関数と setter 関数を使います。以下は first 属性から値を取得する関数 (getter) と、この属性に値を格納する関数 (setter) です:

Noddy_getfirst(Noddy *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Noddy_setfirst(Noddy *self, PyObject *value, void *closure)
{
  if (value == NULL) {
    PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
    return -1;
  }

  if (! PyString_Check(value)) {
    PyErr_SetString(PyExc_TypeError,
                    "The first attribute value must be a string");
    return -1;
  }

  Py_DECREF(self->first);
  Py_INCREF(value);
  self->first = value;

  return 0;
}

getter 関数には Noddy オブジェクトと「閉包 (closure)」 (これは void型のポインタです) が渡されます。今回のケースでは閉包は無視します。 (閉包とは定義データが渡される setter や getter の高度な利用をサポートするためのもので、これを使うとたとえば getter と setter をひとまとめにした関数に、閉包のデータにもとづいて属性を get するか set するか決めさせる、といったことができます。)

setter 関数には Noddy オブジェクトと新しい値、そして閉包が渡されます。新しい値は NULL かもしれず、その場合はこの属性が削除されます。ここでは属性が削除されたり、その値が文字列でないときにはエラーを発生させるようにします。

ここでは PyGetSetDef 構造体の配列をつくります:

static PyGetSetDef Noddy_getseters[] = {
    {"first",
     (getter)Noddy_getfirst, (setter)Noddy_setfirst,
     "first name",
     NULL},
    {"last",
     (getter)Noddy_getlast, (setter)Noddy_setlast,
     "last name",
     NULL},
    {NULL}  /* Sentinel */
};

そしてこれを tp_getset スロットに登録します:

Noddy_getseters,           /* tp_getset */

これで属性の getter と setter が登録できました。

PyGetSetDef 構造体の最後の要素が上で説明した閉包です。今回は閉包は使わないので NULL を渡しています。

また、メンバ定義からはこれらの属性を除いておきましょう:

static PyMemberDef Noddy_members[] = {
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

また、ここでは tp_init ハンドラも渡されるものとして文字列のみを許可するように修正する必要があります 3:

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }

    return 0;
}

これらの変更によって、 first メンバと last メンバが決して NULL にならないと保証できました。これでほとんどすべてのケースから NULL 値のチェックを除けます。これは Py_XDECREF() 呼び出しを Py_DECREF() 呼び出しに変えられることを意味します。唯一これを変えられないのはオブジェクト解放メソッド (deallocator) で、なぜならここではコンストラクタによるメンバ初期化が失敗している可能性があるからです。

さて、先ほどもしたように、このモジュール初期化関数と初期化関数内にあるモジュール名を変更しましょう。そして setup.py ファイルに追加の定義をくわえます。

2.1.3. 循環ガベージコレクションをサポートする

Python は循環ガベージコレクション機能をもっており、これは不要なオブジェクトを、たとえ参照カウントがゼロでなくても、発見することができます。これはオブジェクトの参照が循環しているときに起こりえます。たとえば以下の例を考えてください:

>>> l = []
>>> l.append(l)
>>> del l

この例では、自分自身をふくむリストをつくりました。たとえこのリストを del しても、それは自分自身への参照をまだ持ちつづけますから、参照カウントはゼロにはなりません。嬉しいことに Python には循環ガベージコレクション機能がありますから、最終的にはこのリストが不要であることを検出し、解放できます。

In the second version of the Noddy example, we allowed any kind of object to be stored in the first or last attributes 4. This means that Noddy objects can participate in cycles:

>>> import noddy2
>>> n = noddy2.Noddy()
>>> l = [n]
>>> n.first = l

これは実にばかげた例ですが、すくなくとも Noddy クラスに循環ガベージコレクション機能のサポートを加える口実を与えてくれます。循環ガベージコレクションをサポートするには 2つのタイプスロットを埋め、これらのスロットを許可するようにクラス定義のフラグを設定する必要があります:

#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first;
    PyObject *last;
    int number;
} Noddy;

static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    int vret;

    if (self->first) {
        vret = visit(self->first, arg);
        if (vret != 0)
            return vret;
    }
    if (self->last) {
        vret = visit(self->last, arg);
        if (vret != 0)
            return vret;
    }

    return 0;
}

static int
Noddy_clear(Noddy *self)
{
    PyObject *tmp;

    tmp = self->first;
    self->first = NULL;
    Py_XDECREF(tmp);

    tmp = self->last;
    self->last = NULL;
    Py_XDECREF(tmp);

    return 0;
}

static void
Noddy_dealloc(Noddy* self)
{
    PyObject_GC_UnTrack(self);
    Noddy_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Noddy *self;

    self = (Noddy *)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->first = PyString_FromString("");
        if (self->first == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->last = PyString_FromString("");
        if (self->last == NULL) {
            Py_DECREF(self);
            return NULL;
        }

        self->number = 0;
    }

    return (PyObject *)self;
}

static int
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
{
    PyObject *first=NULL, *last=NULL, *tmp;

    static char *kwlist[] = {"first", "last", "number", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                      &first, &last,
                                      &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_XDECREF(tmp);
    }

    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_XDECREF(tmp);
    }

    return 0;
}


static PyMemberDef Noddy_members[] = {
    {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
     "first name"},
    {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
     "last name"},
    {"number", T_INT, offsetof(Noddy, number), 0,
     "noddy number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Noddy_name(Noddy* self)
{
    static PyObject *format = NULL;
    PyObject *args, *result;

    if (format == NULL) {
        format = PyString_FromString("%s %s");
        if (format == NULL)
            return NULL;
    }

    if (self->first == NULL) {
        PyErr_SetString(PyExc_AttributeError, "first");
        return NULL;
    }

    if (self->last == NULL) {
        PyErr_SetString(PyExc_AttributeError, "last");
        return NULL;
    }

    args = Py_BuildValue("OO", self->first, self->last);
    if (args == NULL)
        return NULL;

    result = PyString_Format(format, args);
    Py_DECREF(args);

    return result;
}

static PyMethodDef Noddy_methods[] = {
    {"name", (PyCFunction)Noddy_name, METH_NOARGS,
     "Return the name, combining the first and last name"
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(Noddy),             /* tp_basicsize */
    0,                         /* tp_itemsize */
    (destructor)Noddy_dealloc, /* 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 |
        Py_TPFLAGS_BASETYPE |
        Py_TPFLAGS_HAVE_GC,    /* tp_flags */
    "Noddy objects",           /* tp_doc */
    (traverseproc)Noddy_traverse,   /* tp_traverse */
    (inquiry)Noddy_clear,           /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Noddy_methods,             /* tp_methods */
    Noddy_members,             /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Noddy_init,      /* tp_init */
    0,                         /* tp_alloc */
    Noddy_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initnoddy4(void)
{
    PyObject* m;

    if (PyType_Ready(&NoddyType) < 0)
        return;

    m = Py_InitModule3("noddy4", module_methods,
                       "Example module that creates an extension type.");

    if (m == NULL)
        return;

    Py_INCREF(&NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
}

traversal メソッドは循環した参照に含まれる可能性のある内部オブジェクトへのアクセスを提供します:

static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    int vret;

    if (self->first) {
        vret = visit(self->first, arg);
        if (vret != 0)
            return vret;
    }
    if (self->last) {
        vret = visit(self->last, arg);
        if (vret != 0)
            return vret;
    }

    return 0;
}

循環した参照に含まれるかもしれない各内部オブジェクトに対して、 traversal メソッドに渡された visit() 関数を呼びます。 visit() 関数は内部オブジェクトと、traversal メソッドに渡された追加の引数 arg を引数としてとります。この関数はこの値が非負の場合に返される整数の値を返します。

Python 2.4 以降では、visit 関数の呼び出しを自動化する Py_VISIT() マクロが用意されています。 Py_VISIT() を使えば、 Noddy_traverse() は次のように簡略化できます:

static int
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
{
    Py_VISIT(self->first);
    Py_VISIT(self->last);
    return 0;
}

注釈

注意: tp_traverse の実装で Py_VISIT() を使うには、その引数に正確に visit および arg という名前をつける必要があります。これは、この退屈な実装に統一性を導入することを促進します。

We also need to provide a method for clearing any subobjects that can participate in cycles.

static int
Noddy_clear(Noddy *self)
{
    PyObject *tmp;

    tmp = self->first;
    self->first = NULL;
    Py_XDECREF(tmp);

    tmp = self->last;
    self->last = NULL;
    Py_XDECREF(tmp);

    return 0;
}

Noddy_clear() 中での一時変数の使い方に注目してください。ここでは、一時変数をつかって各メンバの参照カウントを減らす前にそれらに NULL を代入しています。これは次のような理由によります。すでにお話ししたように、もし参照カウントがゼロになると、このオブジェクトがコールバックされるようになってしまいます。さらに、いまやガベージコレクションをサポートしているため、ガベージコレクション時に実行されるコードについても心配しなくてはなりません。もしガベージコレクションが走っていると、あなたの tp_traverse ハンドラが呼び出される可能性があります。メンバの参照カウントがゼロになった場合に、その値が NULL に設定されていないと Noddy_traverse() が呼ばれる機会はありません。

Python 2.4 以降では、注意ぶかく参照カウントを減らすためのマクロ Py_CLEAR() が用意されています。 Py_CLEAR() を使えば、 Noddy_clear() は次のように簡略化できます:

static int
Noddy_clear(Noddy *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}

Note that Noddy_dealloc() may call arbitrary functions through __del__ method or weakref callback. It means circular GC can be triggered inside the function. Since GC assumes reference count is not zero, we need to untrack the object from GC by calling PyObject_GC_UnTrack() before clearing members. Here is reimplemented deallocator which uses PyObject_GC_UnTrack() and Noddy_clear().

static void
Noddy_dealloc(Noddy* self)
{
    PyObject_GC_UnTrack(self);
    Noddy_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

最後に、 Py_TPFLAGS_HAVE_GC フラグをクラス定義のフラグに加えます:

Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* tp_flags */

これで完了です。 tp_alloc スロットまたは tp_free スロットが書かれていれば、それらを循環ガベージコレクションに使えるよう修正すればよいのです。ほとんどの拡張機能は自動的に提供されるバージョンを使うでしょう。

2.1.4. 他の型のサブクラスを作る

既存の型を継承した新しい拡張型を作成することができます。組み込み型から継承するのは特に簡単です。必要な PyTypeObject を簡単に利用できるからです。それに比べて、 PyTypeObject 構造体を拡張モジュール間で共有するのは難しいです。

次の例では、ビルトインの list 型を継承した Shoddy 型を作成しています。新しい型は通常のリスト型と完全に互換性がありますが、追加で内部のカウンタを増やす increment() メソッドを持っています。

>>> import shoddy
>>> s = shoddy.Shoddy(range(3))
>>> s.extend(s)
>>> print len(s)
6
>>> print s.increment()
1
>>> print s.increment()
2
#include <Python.h>

typedef struct {
    PyListObject list;
    int state;
} Shoddy;


static PyObject *
Shoddy_increment(Shoddy *self, PyObject *unused)
{
    self->state++;
    return PyInt_FromLong(self->state);
}


static PyMethodDef Shoddy_methods[] = {
    {"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,
     PyDoc_STR("increment state counter")},
    {NULL,	NULL},
};

static int
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
{
    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
        return -1;
    self->state = 0;
    return 0;
}


static PyTypeObject ShoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "shoddy.Shoddy",         /* tp_name */
    sizeof(Shoddy),          /* 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 |
        Py_TPFLAGS_BASETYPE, /* tp_flags */
    0,                       /* tp_doc */
    0,                       /* tp_traverse */
    0,                       /* tp_clear */
    0,                       /* tp_richcompare */
    0,                       /* tp_weaklistoffset */
    0,                       /* tp_iter */
    0,                       /* tp_iternext */
    Shoddy_methods,          /* tp_methods */
    0,                       /* tp_members */
    0,                       /* tp_getset */
    0,                       /* tp_base */
    0,                       /* tp_dict */
    0,                       /* tp_descr_get */
    0,                       /* tp_descr_set */
    0,                       /* tp_dictoffset */
    (initproc)Shoddy_init,   /* tp_init */
    0,                       /* tp_alloc */
    0,                       /* tp_new */
};

PyMODINIT_FUNC
initshoddy(void)
{
    PyObject *m;

    ShoddyType.tp_base = &PyList_Type;
    if (PyType_Ready(&ShoddyType) < 0)
        return;

    m = Py_InitModule3("shoddy", NULL, "Shoddy module");
    if (m == NULL)
        return;

    Py_INCREF(&ShoddyType);
    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
}

見てわかるように、ソースコードは前の節の Noddy の時と非常に似ています。違う部分をそれぞれを見ていきます。

typedef struct {
    PyListObject list;
    int state;
} Shoddy;

継承した型のオブジェクトの最初の違いは、親クラスのオブジェクト構造が最初に必要なことです。基底型が既に PyObject_HEAD() を構造体の先頭に持っています。

Python オブジェクトが Shoddy 型のインスタンスだった場合、その PyObject* ポインタは PyListObject* にも Shoddy* にも安全にキャストできます。

static int
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
{
    if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
       return -1;
    self->state = 0;
    return 0;
}

この新しい型の __init__ メソッドで、基底型の __init__ メソッドを呼び出している様子を見ることができます。

このパターンは、カスタムの newdealloc メソッドを実装するときには重要です。継承した型の new メソッドは、 tp_alloc を使ってメモリを割り当てるべきではありません。それは基底型の tp_new を呼出たときに処理されるからです。

Shoddy 型のために PyTypeObject() を埋めるとき、 tp_base() スロットを見つけることができます。クロスプラットフォームのコンパイラに対応するために、直接そのスロットを PyList_Type() で埋めてはいけません。代わりに、後でモジュールの init() 関数の中で行うことができます。

PyMODINIT_FUNC
initshoddy(void)
{
    PyObject *m;

    ShoddyType.tp_base = &PyList_Type;
    if (PyType_Ready(&ShoddyType) < 0)
        return;

    m = Py_InitModule3("shoddy", NULL, "Shoddy module");
    if (m == NULL)
        return;

    Py_INCREF(&ShoddyType);
    PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
}

PyType_Read() を呼ぶ前に、型の構造は tp_base スロットは埋められていなければなりません。継承している新しい型を作るとき、 tp_alloc スロットを PyType_GenericNew() で埋める必要はありません。 -- 基底型のアロケート関数が継承されます。

この後は、 PyType_Ready() 関数を呼び、タイプオブジェクトをモジュールへ追加するのは、基本的な Noddy の例と同じです。

2.2. タイプメソッド

この節ではさまざまな実装可能なタイプメソッドと、それらが何をするものであるかについて、ざっと説明します。

以下は PyTypeObject の定義です。デバッグビルドでしか使われないいくつかのメンバは省いてあります:

typedef struct _typeobject {
    PyObject_VAR_HEAD
    char *tp_name; /* For printing, in format "<module>.<name>" */
    int tp_basicsize, tp_itemsize; /* For allocation */

    /* Methods to implement standard operations */

    destructor tp_dealloc;
    printfunc tp_print;
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    cmpfunc tp_compare;
    reprfunc tp_repr;

    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    /* More standard operations (here for binary compatibility) */

    hashfunc tp_hash;
    ternaryfunc tp_call;
    reprfunc tp_str;
    getattrofunc tp_getattro;
    setattrofunc tp_setattro;

    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    /* Flags to define presence of optional/expanded features */
    long tp_flags;

    char *tp_doc; /* Documentation string */

    /* Assigned meaning in release 2.0 */
    /* call function for all accessible objects */
    traverseproc tp_traverse;

    /* delete references to contained objects */
    inquiry tp_clear;

    /* Assigned meaning in release 2.1 */
    /* rich comparisons */
    richcmpfunc tp_richcompare;

    /* weak reference enabler */
    long tp_weaklistoffset;

    /* Added in release 2.2 */
    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

    /* Attribute descriptor and subclassing stuff */
    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;
    struct _typeobject *tp_base;
    PyObject *tp_dict;
    descrgetfunc tp_descr_get;
    descrsetfunc tp_descr_set;
    long tp_dictoffset;
    initproc tp_init;
    allocfunc tp_alloc;
    newfunc tp_new;
    freefunc tp_free; /* Low-level free-memory routine */
    inquiry tp_is_gc; /* For PyObject_IS_GC */
    PyObject *tp_bases;
    PyObject *tp_mro; /* method resolution order */
    PyObject *tp_cache;
    PyObject *tp_subclasses;
    PyObject *tp_weaklist;

} PyTypeObject;

たくさんの メソッドがありますね。でもそんなに心配する必要はありません。定義したい型があるなら、実装するのはこのうちのごくわずかですむことがほとんどです。

すでに予想されているでしょうが、これらの多様なハンドラについて、これからより詳しい情報を提供します。しかしこれらのメンバが構造体中で定義されている順番は無視します。というのは、これらのメンバの現れる順序は歴史的な遺産によるものだからです。型を初期化するさいに、これらのメンバを正しい順序で並べるよう、くれぐれも注意してください。ふつういちばん簡単なのは、必要なメンバがすべて含まれている (たとえそれらが 0 に初期化されていても) 例をとってきて、自分の型に合わせるよう変更をくわえることです。

char *tp_name; /* For printing */

これは型の名前です。前節で説明したように、これはいろいろな場面で現れ、ほとんどは診断用の目的で使われるものです。なので、そのような場面で役に立つであろう名前を選んでください!

int tp_basicsize, tp_itemsize; /* For allocation */

これらのメンバは、この型のオブジェクトが作成されるときにどれだけのメモリを割り当てればよいのかをランタイムに指示します。Python には可変長の構造体 (文字列やリストなどを想像してください) に対する組み込みのサポートがある程度あり、ここで tp_itemsize メンバが使われます。これらについてはあとでふれます。

char *tp_doc;

ここには Python スクリプトリファレンス obj.__doc__ が doc string を返すときの文字列 (あるいはそのアドレス) を入れます。

では次に、ほとんどの拡張型が実装するであろう基本的なタイプメソッドに入っていきます。

2.2.1. ファイナライズとメモリ解放

destructor tp_dealloc;

型のインスタンスの参照カウントがゼロになり、Python インタプリタがそれを潰して再利用したくなると、この関数が呼ばれます。解放すべきメモリをその型が保持していたり、それ以外にも実行すべき後処理がある場合は、それらをここに入れられます。オブジェクトそれ自体もここで解放される必要があります。この関数の例は、以下のようなものです:

static void
newdatatype_dealloc(newdatatypeobject * obj)
{
    free(obj->obj_UnderlyingDatatypePtr);
    Py_TYPE(obj)->tp_free(obj);
}

解放用関数でひとつ重要なのは、処理待ちの例外にいっさい手をつけないことです。なぜなら、解放用の関数は Python インタプリタがスタックを元の状態に戻すときに呼ばれることが多いからです。そして (通常の関数からの復帰でなく) 例外のためにスタックが巻き戻されるときは、すでに発生している例外から解放用関数を守るものはありません。解放用の関数がおこなう動作が追加の Python のコードを実行してしまうと、それらは例外が発生していることを検知するかもしれません。これはインタプリタが誤解させるエラーを発生させることにつながります。これを防ぐ正しい方法は、安全でない操作を実行する前に処理待ちの例外を保存しておき、終わったらそれを元に戻すことです。これは PyErr_Fetch() および PyErr_Restore() 関数を使うことによって可能になります:

static void
my_dealloc(PyObject *obj)
{
    MyObject *self = (MyObject *) obj;
    PyObject *cbresult;

    if (self->my_callback != NULL) {
        PyObject *err_type, *err_value, *err_traceback;
        int have_error = PyErr_Occurred() ? 1 : 0;

        if (have_error)
            PyErr_Fetch(&err_type, &err_value, &err_traceback);

        cbresult = PyObject_CallObject(self->my_callback, NULL);
        if (cbresult == NULL)
            PyErr_WriteUnraisable(self->my_callback);
        else
            Py_DECREF(cbresult);

        if (have_error)
            PyErr_Restore(err_type, err_value, err_traceback);

        Py_DECREF(self->my_callback);
    }
    Py_TYPE(obj)->tp_free((PyObject*)self);
}

2.2.2. オブジェクト表現

Python では、オブジェクトの文字列表現を生成するのに 3つのやり方があります: repr() 関数 (あるいはそれと等価なバッククォートを用いた表現) を使う方法、 str() 関数を使う方法、そして print 文を使う方法です。ほとんどのオブジェクトで print 文は str() 関数と同じですが、必要な場合には特殊なケースとして FILE* にも表示できます。 FILE* への表示は、効率が問題となっている場合で、一時的な文字列オブジェクトを作成してファイルに書き込むのでは効率が悪すぎることがプロファイリングからも明らかな場合にのみ使うべきです。

これらのハンドラはどれも必須ではありません。ほとんどの型ではせいぜい tp_str ハンドラと tp_repr ハンドラを実装するだけですみます。

reprfunc tp_repr;
reprfunc tp_str;
printfunc tp_print;

tp_repr ハンドラは呼び出されたインスタンスの文字列表現を格納した文字列オブジェクトを返す必要があります。簡単な例は以下のようなものです:

static PyObject *
newdatatype_repr(newdatatypeobject * obj)
{
    return PyString_FromFormat("Repr-ified_newdatatype{{size:\%d}}",
                               obj->obj_UnderlyingDatatypePtr->size);
}

tp_repr ハンドラが指定されていなければ、インタプリタはその型の tp_name とそのオブジェクトの一意な識別値をもちいて文字列表現を作成します。

tp_str ハンドラと str() の関係は、上の tp_repr ハンドラと repr() の関係に相当します。つまり、これは Python のコードがオブジェクトのインスタンスに対して str() を呼び出したときに呼ばれます。この関数の実装は tp_repr ハンドラのそれと非常に似ていますが、得られる文字列表現は人間が読むことを意図されています。 tp_str が指定されていない場合、かわりに tp_repr ハンドラが使われます。

以下は簡単な例です:

static PyObject *
newdatatype_str(newdatatypeobject * obj)
{
    return PyString_FromFormat("Stringified_newdatatype{{size:\%d}}",
                               obj->obj_UnderlyingDatatypePtr->size);
}

print ハンドラは Python がその型のインスタンスを「print する」必要のあるときに毎回呼ばれます。たとえば 'node' が TreeNode 型のインスタンスだとすると、print ハンドラは Python が以下を実行したときに呼ばれます:

print node

flags 引数には Py_PRINT_RAW というフラグがあり、これはその文字列をクォートやおそらくはエスケープシーケンスの解釈もなしで表示することを指示します。

この print 関数は FILE* オブジェクトを引数としてとります。たぶん、ここに出力することになるでしょう。

print 関数の例は以下のようになります:

static int
newdatatype_print(newdatatypeobject *obj, FILE *fp, int flags)
{
    if (flags & Py_PRINT_RAW) {
        fprintf(fp, "<{newdatatype object--size: %d}>",
                obj->obj_UnderlyingDatatypePtr->size);
    }
    else {
        fprintf(fp, "\"<{newdatatype object--size: %d}>\"",
                obj->obj_UnderlyingDatatypePtr->size);
    }
    return 0;
}

2.2.3. 属性を管理する

属性をもつどのオブジェクトに対しても、その型は、それらオブジェクトの属性をどのように解決するか制御する関数を提供する必要があります。必要な関数としては、属性を (それが定義されていれば) 取り出すものと、もうひとつは属性に (それが許可されていれば) 値を設定するものです。属性を削除するのは特殊なケースで、この場合は新しい値としてハンドラに NULL が渡されます。

Python は 2つの属性ハンドラの組をサポートしています。属性をもつ型はどちらか一組を実装するだけでよく、それらの違いは一方の組が属性の名前を char* として受け取るのに対してもう一方の組は属性の名前を PyObject* として受け取る、というものです。それぞれの型はその実装にとって都合がよい方を使えます。

getattrfunc  tp_getattr;        /* char * version */
setattrfunc  tp_setattr;
/* ... */
getattrofunc tp_getattrofunc;   /* PyObject * version */
setattrofunc tp_setattrofunc;

オブジェクトの属性へのアクセスがつねに (すぐあとで説明する) 単純な操作だけならば、 PyObject* を使って属性を管理する関数として、総称的 (generic) な実装を使えます。特定の型に特化した属性ハンドラの必要性は Python 2.2 からほとんど完全になくなりました。しかし、多くの例はまだ、この新しく使えるようになった総称的なメカニズムを使うよう更新されてはいません。

2.2.3.1. 総称的な属性を管理する

バージョン 2.2 で追加.

ほとんどの型は 単純な 属性を使うだけです。では、どのような属性が単純だといえるのでしょうか? それが満たすべき条件はごくわずかです:

  1. PyType_Ready() が呼ばれたとき、すでに属性の名前がわかっていること。

  2. 属性を参照したり設定したりするときに、特別な記録のための処理が必要でなく、また参照したり設定した値に対してどんな操作も実行する必要がないこと。

これらの条件は、属性の値や、値が計算されるタイミング、または格納されたデータがどの程度妥当なものであるかといったことになんら制約を課すものではないことに注意してください。

PyType_Ready() が呼ばれると、これはそのタイプオブジェクトに参照されている 3つのテーブルを使って、そのタイプオブジェクトの辞書中にデスクリプタ(descriptor) を作成します。各デスクリプタは、インスタンスオブジェクトの属性に対するアクセスを制御します。それぞれのテーブルはなくてもかまいません。もしこれら 3つがすべて NULL だと、その型のインスタンスはその基底型から継承した属性だけを持つことになります。また、 tp_getattro および tp_setattroNULL のままだった場合も、基底型にこれらの属性の操作がまかせられます。

テーブルはタイプオブジェクト中の 3つのメンバとして宣言されています:

struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;

tp_methodsNULL でない場合、これは PyMethodDef 構造体への配列を指している必要があります。テーブル中の各エントリは、つぎのような構造体のインスタンスです:

typedef struct PyMethodDef {
    const char  *ml_name;       /* method name */
    PyCFunction  ml_meth;       /* implementation function */
    int          ml_flags;      /* flags */
    const char  *ml_doc;        /* docstring */
} PyMethodDef;

その型が提供する各メソッドについてひとつのエントリを定義する必要があります。基底型から継承してきたメソッドについてはエントリは必要ありません。これの最後には、配列の終わりを示すための見張り番 (sentinel) として追加のエントリがひとつ必要です。この場合、 ml_name メンバが sentinel として使われ、その値は NULL でなければなりません。

XXX Need to refer to some unified discussion of the structure fields, shared with the next section.

2番目のテーブルは、インスタンス中に格納されるデータと直接対応づけられた属性を定義するのに使います。いくつもの C の原始的な型がサポートされており、アクセスを読み込み専用にも読み書き可能にもできます。このテーブルで使われる構造体は次のように定義されています:

typedef struct PyMemberDef {
    char *name;
    int   type;
    int   offset;
    int   flags;
    char *doc;
} PyMemberDef;

このテーブルの各エントリに対してデスクリプタ(descriptor)が作成され、値をインスタンスの構造体から抽出しうる型に対してそれらが追加されます。 type メンバは structmember.h ヘッダで定義された型のコードをひとつ含んでいる必要があります。この値は Python における値と C における値をどのように変換しあうかを定めるものです。 flags メンバはこの属性がどのようにアクセスされるかを制御するフラグを格納するのに使われます。

XXX Need to move some of this to a shared section!

以下のフラグ用定数は structmember.h で定義されており、これらはビットごとの OR を取って組み合わせられます。

定数

意味

READONLY

絶対に変更できない。

RO

READONLY の短縮形。

READ_RESTRICTED

制限モード (restricted mode) では参照できない。

WRITE_RESTRICTED

制限モード (restricted mode) では変更できない。

RESTRICTED

制限モード (restricted mode) では参照も変更もできない。

tp_members を使ったひとつの面白い利用法は、実行時に使われるデスクリプタを作成しておき、単にテーブル中にテキストを置いておくことによって、この方法で定義されたすべての属性に doc string を関連付けられるようにすることです。アプリケーションはこのイントロスペクション用 API を使って、クラスオブジェクトからデスクリプタを取り出し、その __doc__ 属性を使って doc string を得られます。

tp_methods テーブルと同じように、ここでも name メンバの値を NULL にした見張り用エントリが必要です。

2.2.3.2. 特定の型に特化した属性の管理

話を単純にするため、ここでは char* を使ったバージョンのみを示します。name パラメータの型はインターフェイスとして char* を使うか PyObject* を使うかの違いしかありません。この例では、上の総称的な例と同じことを効率的にやりますが、 Python 2.2 で追加された総称的な型のサポートを使わずにやります。これを紹介することは 2つの意味をもっています。ひとつはどうやって、古いバージョンの Python と互換性のあるやり方で、基本的な属性管理をおこなうか。そしてもうひとつはハンドラの関数がどのようにして呼ばれるのか。これで、たとえその機能を拡張する必要があるとき、何をどうすればいいかわかるでしょう。

tp_getattr ハンドラはオブジェクトが属性への参照を要求するときに呼ばれます。これは、そのクラスの __getattr__() メソッドが呼ばれるであろう状況と同じ状況下で呼び出されます。

これを処理するありがちな方法は、(1) 一連の関数 (下の例の newdatatype_getSize()newdatatype_setSize()) を実装する、(2) これらの関数を記録したメソッドテーブルを提供する、そして (3) そのテーブルの参照結果を返す getattr 関数を提供することです。メソッドテーブルはタイプオブジェクトの tp_methods メンバと同じ構造を持っています。

以下に例を示します:

static PyMethodDef newdatatype_methods[] = {
    {"getSize", (PyCFunction)newdatatype_getSize, METH_VARARGS,
     "Return the current size."},
    {"setSize", (PyCFunction)newdatatype_setSize, METH_VARARGS,
     "Set the size."},
    {NULL, NULL, 0, NULL}           /* sentinel */
};

static PyObject *
newdatatype_getattr(newdatatypeobject *obj, char *name)
{
    return Py_FindMethod(newdatatype_methods, (PyObject *)obj, name);
}

tp_setattr ハンドラは、クラスのインスタンスの __setattr__() または __delattr__() メソッドが呼ばれるであろう状況で呼び出されます。ある属性が削除されるとき、3番目のパラメータは NULL になります。以下の例はたんに例外を発生させるものですが、もし本当にこれと同じことをしたいなら、 tp_setattr ハンドラを NULL に設定すべきです。

static int
newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)
{
    (void)PyErr_Format(PyExc_RuntimeError, "Read-only attribute: \%s", name);
    return -1;
}

2.2.4. オブジェクトの比較

cmpfunc tp_compare;

tp_compare ハンドラは、オブジェクトどうしの比較が必要で、そのオブジェクトに要求された比較をおこなうのに適した特定の拡張比較メソッドが実装されていないときに呼び出されます。(これが定義されているとき、 PyObject_Compare() または PyObject_Cmp() が使われるとこれはつねに呼び出されます、また Python で cmp() が使われたときにも呼び出されます。) これは __cmp__() メソッドに似ています。この関数はもし obj1obj2 より「小さい」場合は -1 を返し、それらが等しければ 0 、そしてもし obj1obj2 より「大きい」場合は 1 を返す必要があります。 (以前は大小比較の結果として、任意の大きさの負または正の整数を返せましたが、 Python 2.2 以降ではこれはもう許されていません。将来的には、上にあげた以外の返り値は別の意味をもつ可能性があります。)

tp_compare ハンドラは例外を発生させられます。この場合、この関数は負の値を返す必要があります。呼び出した側は PyErr_Occurred() を使って例外を検査しなければなりません。

以下はサンプル実装です:

static int
newdatatype_compare(newdatatypeobject * obj1, newdatatypeobject * obj2)
{
    long result;

    if (obj1->obj_UnderlyingDatatypePtr->size <
        obj2->obj_UnderlyingDatatypePtr->size) {
        result = -1;
    }
    else if (obj1->obj_UnderlyingDatatypePtr->size >
             obj2->obj_UnderlyingDatatypePtr->size) {
        result = 1;
    }
    else {
        result = 0;
    }
    return result;
}

2.2.5. 抽象的なプロトコルのサポート

Python はいくつもの 抽象的な “プロトコル”をサポートしています。これらを使用する特定のインターフェイスについては 抽象オブジェクトレイヤ (abstract objects layer) で解説されています。

これら多数の抽象的なインターフェイスは、Python の実装が開発される初期の段階で定義されていました。とりわけ数値や辞書、そしてシーケンスなどのプロトコルは最初から Python の一部だったのです。それ以外のプロトコルはその後追加されました。型の実装にあるいくつかのハンドラルーチンに依存するようなプロトコルのために、古いプロトコルはハンドラの入ったオプションのブロックとして定義し、型オブジェクトから参照するようになりました。タイプオブジェクトの主部に追加のスロットをもつ新しいプロトコルについては、フラグ用のビットを立てることでそれらのスロットが存在しており、インタプリタがチェックすべきであることを指示できます。(このフラグ用のビットは、そのスロットの値が非 NULL であることを示しているわけではありません。フラグはスロットの存在を示すのに使えますが、そのスロットはまだ埋まっていないかもしれないのです。)

PyNumberMethods   *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods  *tp_as_mapping;

お使いのオブジェクトを数値やシーケンス、あるいは辞書のようにふるまうようにしたいならば、それぞれに C の PyNumberMethods 構造体、 PySequenceMethods 構造体、または PyMappingMethods 構造体のアドレスを入れます。これらに適切な値を入れても入れなくてもかまいません。これらを使った例は Python の配布ソースにある Objects でみつけることができるでしょう。

hashfunc tp_hash;

この関数は、もし使うのならば、これはお使いの型のインスタンスのハッシュ番号を返すようにします。以下はやや的はずれな例ですが

static long
newdatatype_hash(newdatatypeobject *obj)
{
    long result;
    result = obj->obj_UnderlyingDatatypePtr->size;
    result = result * 3;
    return result;
}
ternaryfunc tp_call;

この関数は、その型のインスタンスが「関数として呼び出される」ときに呼ばれます。たとえばもし obj1 にそのインスタンスが入っていて、Python スクリプトで obj1('hello') を実行したとすると、 tp_call ハンドラが呼ばれます。

この関数は 3つの引数をとります:

  1. arg1 にはその呼び出しの対象となる、そのデータ型のインスタンスが入ります。たとえば呼び出しが obj1('hello') の場合、 arg1obj1 になります。

  2. arg2 は呼び出しの引数を格納しているタプルです。ここから引数を取り出すには PyArg_ParseTuple() を使います。

  3. arg3 はキーワード引数のための辞書です。これが NULL 以外でキーワード引数をサポートしているなら、 PyArg_ParseTupleAndKeywords() をつかって引数を取り出せます。キーワード引数をサポートしていないのにこれが NULL 以外の場合は、キーワード引数はサポートしていない旨のメッセージとともに TypeError を発生させてください。

以下はこの call 関数をてきとうに使った例です。

/* Implement the call function.
 *    obj1 is the instance receiving the call.
 *    obj2 is a tuple containing the arguments to the call, in this
 *         case 3 strings.
 */
static PyObject *
newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)
{
    PyObject *result;
    char *arg1;
    char *arg2;
    char *arg3;

    if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
        return NULL;
    }
    result = PyString_FromFormat(
        "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",
        obj->obj_UnderlyingDatatypePtr->size,
        arg1, arg2, arg3);
    printf("\%s", PyString_AS_STRING(result));
    return result;
}

XXX some fields need to be added here...

/* Added in release 2.2 */
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;

これらの関数はイテレータ用プロトコルをサポートします。オブジェクトが、その (ループ中に順に生成されていくかもしれない) 内容を巡回 (訳注: イテレータでひとつずつ要素をたどっていくこと) するイテレータをサポートしたい場合は、 tp_iter ハンドラを実装する必要があります。 tp_iter ハンドラによって返されるオブジェクトは tp_itertp_iternext の両方を実装する必要があります。どちらのハンドラも、それが呼ばれたインスタンスをひとつだけ引数としてとり、新しい参照を返します。エラーが起きた場合には例外を設定してから NULL を返す必要があります。

巡回可能な要素を表現するオブジェクトに対しては、 tp_iter ハンドラがイテレータオブジェクトを返す必要があります。イテレータオブジェクトは巡回中の状態を保持する責任をもっています。お互いに干渉しない複数のイテレータの存在を許すようなオブジェクト (リストやタプルがそうです) の場合は、新しいイテレータを作成して返す必要があります。 (巡回の結果生じる副作用のために) 一回だけしか巡回できないオブジェクトの場合は、それ自身への参照を返すようなハンドラと、 tp_iternext ハンドラも実装する必要があります。ファイルオブジェクトはそのようなイテレータの例です。

イテレータオブジェクトは両方のハンドラを実装する必要があります。 tp_iter ハンドラはそのイテレータへの新しい参照を返します (これは破壊的にしか巡回できないオブジェクトに対する tp_iter ハンドラと同じです)。 tp_iternext ハンドラはその次のオブジェクトがある場合、それへの新しい参照を返します。巡回が終端に達したときは例外を出さずに NULL を返してもいいですし、 StopIteration を放出してもかまいません。例外を使わないほうがやや速度が上がるかもしれません。実際のエラーが起こったときには、例外を放出して NULL を返す必要があります。

2.2.6. 弱参照(Weak Reference)のサポート

Pythonの弱参照実装のひとつのゴールは、どのような(数値のような弱参照による利益を得ない)タイプでもオーバーヘッドなしで弱参照のメカニズムに組み込めるようにすることです。

弱参照可能なオブジェクトの拡張では、弱参照メカニズムのために PyObject* フィールドをインスタンス構造体に含む必要があります。これはオブジェクトのコンストラクタで NULL に初期化する必要があります。これは対応するタイプの tp_weaklistoffset フィールドをフィールドのオフセットに設定しなければいけません。たとえば、インスタンスタイプは以下の構造体で定義されます:

typedef struct {
    PyObject_HEAD
    PyClassObject *in_class;       /* The class object */
    PyObject      *in_dict;        /* A dictionary */
    PyObject      *in_weakreflist; /* List of weak references */
} PyInstanceObject;

インスタンス用に静的に宣言されたタイプオブジェクトはこのように定義されます:

PyTypeObject PyInstance_Type = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,
    "module.instance",

    /* Lots of stuff omitted for brevity... */

    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
    0,                                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(PyInstanceObject, in_weakreflist), /* tp_weaklistoffset */
};

タイプのコンストラクタは弱参照を NULL に初期化する責任があります:

static PyObject *
instance_new() {
    /* Other initialization stuff omitted for brevity */

    self->in_weakreflist = NULL;

    return (PyObject *) self;
}

ほかに追記すべきことは、デストラクタは弱参照を消すために弱参照のマネージャを呼ぶ必要があることくらいです。これは弱参照リストが NULL でない場合にだけ必要です:

static void
instance_dealloc(PyInstanceObject *inst)
{
    /* Allocate temporaries if needed, but do not begin
       destruction just yet.
     */

    if (inst->in_weakreflist != NULL)
        PyObject_ClearWeakRefs((PyObject *) inst);

    /* Proceed with object destruction normally. */
}

2.2.7. その他いろいろ

上にあげたほとんどの関数は、その値として 0 を与えれば省略できることを忘れないでください。それぞれの関数で提供しなければならない型の定義があり、これらは Python の include 用ディレクトリの object.h というファイルにおさめられています。これは Python の配布ソースに含まれています。

新しいデータ型に何らかのメソッドを実装するやりかたを学ぶには、以下の方法がおすすめです: Python の配布されているソースをダウンロードして展開する。 Objects ディレクトリへ行き、C のソースファイルから「 tp_ 欲しい名前」の文字列で検索する (たとえば tp_print とか tp_compare のように)。こうすれば実装したい例がみつかるでしょう。

あるオブジェクトが、いま実装している型のインスタンスであるかどうかを確かめたい場合には、 PyObject_TypeCheck() 関数を使ってください。使用例は以下のようなかんじです:

if (! PyObject_TypeCheck(some_object, &MyType)) {
    PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");
    return NULL;
}

注記

1

これはそのオブジェクトが文字列や実数などの基本タイプであるような時に成り立ちます。

2

ここで出てきたタイプではガベージコレクションをサポートしていないので、この例では tp_dealloc ハンドラに依存しています。このハンドラはそのタイプがたとえガベージコレクションをサポートしている場合でも、そのオブジェクトの「追跡を解除する」ために呼ばれることがありますが、これは高度な話題でありここでは扱いません。

3

first および last メンバが文字列であるということはわかっているので、いまやそれらの参照カウントを減らすときにはそれほど注意する必要はないように思えるかもしれません。しかし文字列型のサブクラスは依然として受けつけられています。通常の文字列型ならば、解放時にあなたのオブジェクトがコールバックされることはありませんが、文字列型のサブクラスがそうしないという保証はありません。

4

3番目のバージョンでさえ、循環を回避できるという保証はされていません。たとえ通常の文字列型なら循環しない場合でも、文字列型のサブクラスをとることが許されていれば、そのタイプでは循環が発生しうるからです。