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

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

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

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

Noddy クラスの 2番目の例では、first 属性と last 属性にどんなオブジェクトでも格納できるようになっていました。 2.4。つまり、Noddy オブジェクトの参照は循環しうるのです:

>>> 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)
{
    Noddy_clear(self);
    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 | 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 という名前をつける必要があります。 これは、この退屈な実装に統一性を導入することを促進します。

また、循環した参照に含まれた内部オブジェクトを消去するためのメソッドも 提供する必要があります。オブジェクト解放用のメソッドを再実装して、 このメソッドに使いましょう:

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)
{
    Noddy_clear(self);
    self->ob_type->tp_free((PyObject*)self);
}

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;
}

さいごに、Py_TPFLAGS_HAVE_GC フラグを クラス定義のフラグに加えます:

    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/

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



脚注

... 属性にどんなオブジェクトでも格納できるようになっていました。2.4
3番目のバージョンでさえ、循環を回避できるという保証は されていません。たとえ通常の文字列型なら循環しない場合でも、文字列型の サブクラスをとることが許されていれば、そのタイプでは循環が発生 しうるからです。
ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。