オブジェクトの__class__属性を得るまでの流れ

環境

この記事の内容は、Python 3.0rc1で確認しました。

まとめ

__class__属性には、PyGetSetDescrObjectオブジェクトを介して読み書きします。

クラスに__class__属性を登録するまでの流れ

Objects/typeobject.cで、__class__属性の名前と、属性を読み書きするための関数が、静的変数object_getsetsに設定されています。

static PyGetSetDef object_getsets[] = {
        {"__class__", object_get_class, object_set_class,
         PyDoc_STR("the object's class")},
        {0}
};

ここで、PyGetSetDef構造体は、Include/descrobject.hで以下のように定義されています。

typedef PyObject *(*getter)(PyObject *, void *);
typedef int (*setter)(PyObject *, PyObject *, void *);

typedef struct PyGetSetDef {
        char *name;
        getter get;
        setter set;
        char *doc;
        void *closure;
} PyGetSetDef;

また、object_get_class関数は、Objects/typeobject.cにあります。

static PyObject *
object_get_class(PyObject *self, void *closure) 
{
        Py_INCREF(Py_TYPE(self));
        return (PyObject *)(Py_TYPE(self));
}

このobject_getsets変数は、Objects/typeobject.cで、PyBaseObject_Type変数のtp_getsetメンバに設定されます。

PyTypeObject PyBaseObject_Type = {
:
        object_getsets,                         /* tp_getset */

このtp_getsetメンバに設定されたPyGetSetDef構造体の配列は、Objects/typeobjects.cのPyType_Ready関数内で、属性に加えられます。PyType_Ready関数は、クラスを初期設定する関数で、クラスが必要になったときに呼び出されます(二回目以降の呼び出しでは、何もしません)。

int
PyType_Ready(PyTypeObject *type)
{
:
        if (type->tp_getset != NULL) {
                if (add_getset(type, type->tp_getset) < 0)
                        goto error;
        }

実際にクラスに属性を登録するのは、add_getset関数です。エラーのチェックやリファレンスカウントに関するコードを除いたadd_getset関数を、以下に記します。

static int
add_getset(PyTypeObject *type, PyGetSetDef *gsp)
{
        PyObject *dict = type->tp_dict;

        for (; gsp->name != NULL; gsp++) {
                PyObject *descr = PyDescr_NewGetSet(type, gsp);
                PyDict_SetItemString(dict, gsp->name, descr);
        }
        return 0;
}

属性の値として登録されるのは、PyGetSetDescrObjectオブジェクトです。PyGetSetDescrObject構造体の定義は、Include/descrobject.hにあります。

#define PyDescr_COMMON \
        PyObject_HEAD \
        PyTypeObject *d_type; \
        PyObject *d_name
:
typedef struct {
        PyDescr_COMMON;
        PyGetSetDef *d_getset;
} PyGetSetDescrObject;


このオブジェクトは、Objects/descrobject.cのPyDescr_NewGetSet関数で生成されます。

PyObject *
PyDescr_NewGetSet(PyTypeObject *type, PyGetSetDef *getset)
{
        PyGetSetDescrObject *descr;

        descr = (PyGetSetDescrObject *)descr_new(&PyGetSetDescr_Type,
                                                 type, getset->name);
        if (descr != NULL)
                descr->d_getset = getset;
        return (PyObject *)descr;
}

ここまでの流れを追えば、最初のobject_getsets変数が、PyGetSetDescrObjectオブジェクトのd_getsetメンバに設定されるということが分かると思います。

__class__属性を得るまでの流れ

オブジェクトの属性を取得するのは、Objects/object.cのPyObject_GenericGetAttr関数です。今回の話に関係ある所だけ、抜き出して記載します。

PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
        PyTypeObject *tp = Py_TYPE(obj);
:
        descr = _PyType_Lookup(tp, name);
:
        Py_XINCREF(descr);

        f = NULL;
        if (descr != NULL) {
                f = descr->ob_type->tp_descr_get;
                if (f != NULL && PyDescr_IsData(descr)) {
                        res = f(descr, obj, (PyObject *)obj->ob_type);
                        Py_DECREF(descr);
                        goto done;
                }
        }

_PyType_Lookup関数は、クラスの属性を得る関数です(ソースコードは省略します)。上でみた通り、__class__属性にはPyGetSetDescrObjectオブジェクトが登録されているので、descr変数もPyGetSetDescrObjectオブジェクトになります。PyDescr_NewGetSet関数からも類推できますが、このオブジェクトの型は、Objects/descrobject.cのPyGetSetDescr_Typeです。

PyTypeObject PyGetSetDescr_Type = {
:
        (descrgetfunc)getset_get,               /* tp_descr_get */

よって、PyObject_GenericGetAttr関数の変数fの値は、Objects/descrobject.cのgetset_get関数です。この関数の実装は、以下の通りです。

static PyObject *
getset_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type)
{
:
        if (descr->d_getset->get != NULL)
                return descr->d_getset->get(obj, descr->d_getset->closure);

ここで、PyGetSetDescrObjectオブジェクトのd_getsetメンバが、PyGetSetDef構造体であることを思い出して下さい。ここでようやく、最初のobject_getsets変数に設定されていた関数object_get_classが呼び出され、__class__属性を得ることができます。

雑感

なんか、回りくどいです。