オブジェクトの__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__属性を得ることができます。
雑感
なんか、回りくどいです。