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);
    self->ob_type->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 = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "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);
    self->ob_type->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 メンバに アクセスしようとする前に、そのデストラクタが呼び出されてしまうかもしれないのです。 このような可能性からパラノイア的に身をまもるため、ほとんどの場合 メンバへの代入は,その参照カウントを減らす前におこなってください。 こうする必要がないのはどんな場合でしょうか?

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

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.2
We relied ここで出てきたタイプではガベージコレクションをサポートしていないので、 この例では tp_dealloc ハンドラに依存しています。このハンドラは そのタイプがたとえガベージコレクションをサポートしている場合でも、そのオブジェクトの 「追跡を解除する」ために呼ばれることがありますが、これは高度な話題であり ここでは扱いません。
ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。