[[Pythonを読む]]
 
 #contents
 
 *はじめに [#v4d88ea9]
 
 ようやく__build_class__関数の実装であるbuiltin___build_class__に行きつきました。今回はこの関数が何をしているのかを見ていきます。
 
 builtin___build_class__は長いので段階的に見ていきます。大きく分けると以下の処理を行っています。
 
 +メタクラスの決定
 +クラス(メソッドなど)を定義するコードを実行し属性辞書を作成
 +属性辞書を使ってクラスを作成
 
 *メタクラスの決定 [#g240d9e5]
 
 長くなるので引数チェック(エラー終了)系はカットします。
 
 #code(C){{
 static PyObject *
 builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
 {
     PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns;
     PyObject *cls = NULL, *cell = NULL;
     Py_ssize_t nargs;
     int isclass = 0;   /* initialize to prevent gcc warning */
 
     nargs = PyTuple_GET_SIZE(args);
     func = PyTuple_GET_ITEM(args, 0); /* Better be callable */
     name = PyTuple_GET_ITEM(args, 1);
     bases = PyTuple_GetSlice(args, 2, nargs);
 
     if (kwds == NULL) {
         meta = NULL;
         mkw = NULL;
     }
     else {
         // メタクラスが指定された場合の処理
     }
     if (meta == NULL) {
         /* if there are no bases, use type: */
         if (PyTuple_GET_SIZE(bases) == 0) {
             meta = (PyObject *) (&PyType_Type);
         }
         /* else get the type of the first base */
         else {
             PyObject *base0 = PyTuple_GET_ITEM(bases, 0);
             meta = (PyObject *) (base0->ob_type);
         }
         Py_INCREF(meta);
         isclass = 1;  /* meta is really a class */
     }
     if (isclass) {
         /* meta is really a class, so check for a more derived
            metaclass, or possible metaclass conflicts: */
         winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta,
                                                         bases);
         if (winner != meta) {
             Py_DECREF(meta);
             meta = winner;
             Py_INCREF(meta);
         }
     }
     /* else: meta is not a class, so we cannot do the metaclass
        calculation, so we will use the explicitly given object as it is */
 }}
 
 まずはここまで。kwdsがNULLじゃないときというのは「class Foo(metaclass=Meta)」みたいにメタクラスを指定したときです。
 今回はメタクラスは指定しておらず、またスーパークラスも指定していません。この場合、typeがメタクラスになります。メタクラスがtypeに決定されたらisclassは0でいい気がするんですけどね。(実際、_PyType_CalculateMetaclassからはmetaがそのまま返されます)
 
 *クラス定義コード実行(属性辞書の作成) [#f5c7c1c7]
 
 #code(C){{
     prep = _PyObject_GetAttrId(meta, &PyId___prepare__);
     if (prep == NULL) {
         // こっちには来ないはず
     }
     else {
         PyObject *pargs[2] = {name, bases};
         ns = _PyObject_FastCallDict(prep, pargs, 2, mkw);
         Py_DECREF(prep);
     }
     cell = PyEval_EvalCodeEx(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns,
                              NULL, 0, NULL, 0, NULL, 0, NULL,
                              PyFunction_GET_CLOSURE(func));
 }}
 
 本筋ではない、と言ってもいずれは見ないといけないのでちゃんと追っかけていきましょう。具体的には_PyObject_GetAttrIdのことです。
 
 ** _PyObject_GetAttrId (Objects/object.c) [#j865bb14]
 
 まず_PyObject_GetAttrId。これはPyObject_GetAttrを呼んでるだけです。
 
 #code(C){{
 PyObject *
 _PyObject_GetAttrId(PyObject *v, _Py_Identifier *name)
 {
     PyObject *result;
     PyObject *oname = _PyUnicode_FromId(name); /* borrowed */
     if (!oname)
         return NULL;
     result = PyObject_GetAttr(v, oname);
     return result;
 }
 }}
 
 続いてPyObject_GetAttr
 
 #code(C){{
 PyObject *
 PyObject_GetAttr(PyObject *v, PyObject *name)
 {
     PyTypeObject *tp = Py_TYPE(v);
 
     if (!PyUnicode_Check(name)) {
         PyErr_Format(PyExc_TypeError,
                      "attribute name must be string, not '%.200s'",
                      name->ob_type->tp_name);
         return NULL;
     }
     if (tp->tp_getattro != NULL)
         return (*tp->tp_getattro)(v, name);
     if (tp->tp_getattr != NULL) {
         char *name_str = PyUnicode_AsUTF8(name);
         if (name_str == NULL)
             return NULL;
         return (*tp->tp_getattr)(v, name_str);
     }
     PyErr_Format(PyExc_AttributeError,
                  "'%.50s' object has no attribute '%U'",
                  tp->tp_name, name);
     return NULL;
 }
 }}
 
 はい、TypeObjectが出てきました。さらにそのメンバの関数ポインタを使って関数を実行しています。
 
 ここでType_Typeの定義(Objects/typeobject.c)を見てみる。
 
 #code(C){{
 PyTypeObject PyType_Type = {
     PyVarObject_HEAD_INIT(&PyType_Type, 0)
     // 省略
     (getattrofunc)type_getattro,                /* tp_getattro */
     (setattrofunc)type_setattro,                /* tp_setattro */
 }}
 
 というわけで、type_getattroが呼び出されることになります。
 
 #code(C){{
 static PyObject *
 type_getattro(PyTypeObject *type, PyObject *name)
 {
     PyTypeObject *metatype = Py_TYPE(type);
     PyObject *meta_attribute, *attribute;
     descrgetfunc meta_get;
 
     /* Initialize this type (we'll assume the metatype is initialized) */
     if (type->tp_dict == NULL) {
         if (PyType_Ready(type) < 0)
             return NULL;
     }
 
     /* No readable descriptor found yet */
     meta_get = NULL;
 
     /* Look for the attribute in the metatype */
     meta_attribute = _PyType_Lookup(metatype, name);
 
     if (meta_attribute != NULL) {
         // デスクリプタの処理。省略
 
         Py_INCREF(meta_attribute);
     }
 
     /* No data descriptor found on metatype. Look in tp_dict of this
      * type and its bases */
     attribute = _PyType_Lookup(type, name);
     if (attribute != NULL) {
         /* Implement descriptor functionality, if any */
         descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get;
 
         Py_XDECREF(meta_attribute);
 
         // デスクリプタの処理。省略
 
         Py_INCREF(attribute);
         return attribute;
     }
 
     // 今回のケースではここまでで返ってると思うので省略
 }
 }}
 
 今の場合、typeはType_Typeが渡されているので、TYPEはType_Typeです(ややこしい)。というわけで、typeもmetatype同じものを指すことになり、結局「return attribute」のところで指定された属性__prepare__が返されるはず。
 
 _PyType_Lookupは基本的にはtp_dictから指定された属性を取得する関数です。基本的じゃない点としてはメソッドキャッシュしたり継承さかのぼって属性探したりとかしているわけですが、今回はあまり関係ないので追っかけるのはやめておきます。
 
 ** PyType_Ready (Objects/typeobject.c) [#e8aab319]
 
 さて、追っかけないといけない方の話をしましょう。PyType_ReadyにてTypeObjectの初期化が行われているようです。
 なお、実際には_Py_InitializeEx_Privateの中で_Py_ReadyTypes(Objects/object.c)が呼ばれおり、そこで一連のTypeObjectについてPyType_Readyが呼ばれている、つまりバイトコードを動かす段階まで来ているのであればType_Typeの初期化はすでに完了しているという状態です。
 
 PyType_Readyは長いので、__prepare__がtp_dictに登録される周辺部分だけ確認しましょう。心配しなくても(?)また出てきます。とりあえず__prepare__はここに書かれています。
 
 #code(C){{
 static PyObject *
 type_prepare(PyObject *self, PyObject *args, PyObject *kwds)
 {
     return PyDict_New();
 }
 
 static PyMethodDef type_methods[] = {
     // 省略
     {"__prepare__", (PyCFunction)type_prepare,
      METH_VARARGS | METH_KEYWORDS | METH_CLASS,
      PyDoc_STR("__prepare__() -> dict\n"
                "used to create the namespace for the class statement")},
     // 省略
     {0}
 };
 }}
 
 PyMethodDefは前にbultinでも出てきましたね。type_methodsはType_Type定義時に指定されています。
 
 #code(C){{
 PyTypeObject PyType_Type = {
     PyVarObject_HEAD_INIT(&PyType_Type, 0)
     // 省略
     type_members,                               /* tp_members */
     type_members,                               /* tp_members */
     type_getsets,                               /* tp_getset */
 }}
 
 で、PyType_Ready中の登録してそうなところ
 
 #code(C){{
     /* Initialize tp_dict */
     dict = type->tp_dict;
     if (dict == NULL) {
         dict = PyDict_New();
         if (dict == NULL)
             goto error;
         type->tp_dict = dict;
     }
 
     /* Add type-specific descriptors to tp_dict */
     if (add_operators(type) < 0)
         goto error;
     if (type->tp_methods != NULL) {
         if (add_methods(type, type->tp_methods) < 0)
             goto error;
     }
     if (type->tp_members != NULL) {
         if (add_members(type, type->tp_members) < 0)
             goto error;
     }
     if (type->tp_getset != NULL) {
         if (add_getset(type, type->tp_getset) < 0)
             goto error;
     }
 }}
 
 add_methodsに進む。なお、add_operatorはTypeObjectに直接書かれているtp_reprなどの特殊メソッドの追加、add_membersは構造体メンバにアクセスするためのデスクリプタの追加、add_getsetは同じくデスクリプタなんだけど実装を個別に書ける式のようです。
 
 #code(C){{
 static int
 add_methods(PyTypeObject *type, PyMethodDef *meth)
 {
     PyObject *dict = type->tp_dict;
 
     for (; meth->ml_name != NULL; meth++) {
         PyObject *descr;
         int err;
         if (PyDict_GetItemString(dict, meth->ml_name) && !(meth->ml_flags & METH_COEXIST))
                 continue;
         if (meth->ml_flags & METH_CLASS) {
             descr = PyDescr_NewClassMethod(type, meth);
         }
         else if (meth->ml_flags & METH_STATIC) {
             // 省略
         }
         else {
             descr = PyDescr_NewMethod(type, meth);
         }
         if (descr == NULL)
             return -1;
         err = PyDict_SetItemString(dict, meth->ml_name, descr);
         Py_DECREF(descr);
         if (err < 0)
             return -1;
     }
     return 0;
 }
 }}
 
 あれ?デスクリプタ?先ほどtype_getattroでデスクリプタの場合の処理省略したけどそっちが使われてるんですね。まあともかくもう少し先まで確認。デスクリプタ関連はObject/descrobject.cに書かれています。
 
 #code(C){{
 PyObject *
 PyDescr_NewClassMethod(PyTypeObject *type, PyMethodDef *method)
 {
     PyMethodDescrObject *descr;
 
     descr = (PyMethodDescrObject *)descr_new(&PyClassMethodDescr_Type,
                                              type, method->ml_name);
     if (descr != NULL)
         descr->d_method = method;
     return (PyObject *)descr;
 }
 }}
 
 **type_getattro再び [#xb78dfc0]
 
 では読み違えていたところに戻りましょう。
 
 #code(C){{
 static PyObject *
 type_getattro(PyTypeObject *type, PyObject *name)
 {
     // 省略
 
     if (meta_attribute != NULL) {
         meta_get = Py_TYPE(meta_attribute)->tp_descr_get;
 
         if (meta_get != NULL && PyDescr_IsData(meta_attribute)) {
             /* Data descriptors implement tp_descr_set to intercept
              * writes. Assume the attribute is not overridden in
              * type's tp_dict (and bases): call the descriptor now.
              */
             return meta_get(meta_attribute, (PyObject *)type,
                             (PyObject *)metatype);
         }
         Py_INCREF(meta_attribute);
     }
 
     /* No data descriptor found on metatype. Look in tp_dict of this
      * type and its bases */
     attribute = _PyType_Lookup(type, name);
     if (attribute != NULL) {
         /* Implement descriptor functionality, if any */
         descrgetfunc local_get = Py_TYPE(attribute)->tp_descr_get;
 
         Py_XDECREF(meta_attribute);
 
         if (local_get != NULL) {
             /* NULL 2nd argument indicates the descriptor was
              * found on the target object itself (or a base)  */
             return local_get(attribute, (PyObject *)NULL,
                              (PyObject *)type);
         }
 
         Py_INCREF(attribute);
         return attribute;
     }
 }}
 
 前述したように今はtypeもmetatype同じになりますが、metaの方はPyDescr_IsDataの条件(tp_descr_setが定義されていたら真)があるので実際に呼び出されるのはlocal_getの方です。
 
 というわけでdescr_getとして呼び出されるclassmethod_get。エラー処理削ると一行だけです。
 
 #code(C){{
 static PyObject *
 classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)
 {
     return PyCFunction_NewEx(descr->d_method, type, NULL);
 }
 }}
 
 ここでCFunctionObjectにラップされるか。
 
 回り道が長引いたので一旦ここで区切ります。
 
 *おわりに [#b9ab35bb]
 
 今回は__build_class__、というよりCでのオブジェクト実装の動作について見てきました。その際、PyTypeObjectに登録されている関数が呼び出されていることがわかりました。
 
 また、PyTypeObjectに直接書かれている特殊メソッドおよび通常のメソッドなどはPyTypeObjectの辞書にデスクリプタとして登録されることがわかりました。デスクリプタじゃなくてもいいように思うのですが何か理由があるのでしょう(何かの理由は、必要があったら見ることにします)
 

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS