[[Pythonを読む]]
 
 #contents
 
 *はじめに [#l29d1320]
 
 引き続きbuiltin___build_class__を読んでいきます。builtin___build_class__では以下の処理を行っていました。
 
 +メタクラスの決定
 +クラス(メソッドなど)を定義するコードを実行し属性辞書を作成
 +属性辞書を使ってクラスを作成
 
 「クラス定義コード実行(属性辞書の作成)」の部分
 
 #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_FastCallDictはCFunctionObjectの場合、_PyCFunction_FastCallKeywordsに処理が移譲されています。つまり、Cの関数が呼ばれて結果が返されます。結局、nsは空の辞書オブジェクトを参照することになります。
 
 *PyEval_EvalCodeEx (Python/ceval.c) [#a22d3c92]
 
 さて、PyEval_EvalCodeExを見ていきましょう。前にも見ましたがその時はあまり変数について気にしませんでした。今回は変数が重要になってくるのでその部分をちゃんと確認しましょう。そもそもクラス定義コード実行ってどういうことでしょうか。
 
 #code(C){{
 PyObject *
 PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
            PyObject **args, int argcount, PyObject **kws, int kwcount,
            PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure)
 {
     return _PyEval_EvalCodeWithName(_co, globals, locals,
                                     args, argcount,
                                     kws, kws != NULL ? kws + 1 : NULL,
                                     kwcount, 2,
                                     defs, defcount,
                                     kwdefs, closure,
                                     NULL, NULL);
 }
 }}
 
 呼び出しと照らし合わせると、__prepare__により作成された空の辞書オブジェクトはlocalsにあたることがわかります。globalsは今回は関係ないので無視。ちなみに、クラス定義でクロージャが出てくるのは関数が渡された引数使ってクラス定義するときぐらいですね。そんな処理が必要になるのはDjangoとかのフレームワークぐらいだと思いますが(笑)
 
 _PyEval_EvalCodeWithNameに移ります。localsの動きを見てみるとPyFrame_Newに渡され、その後出てきません。つまり、PyFrame_Newでもう設定は完了しているということになります。
 
 #code(C){{
 static PyObject *
 _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
            PyObject **args, Py_ssize_t argcount,
            PyObject **kwnames, PyObject **kwargs,
            Py_ssize_t kwcount, int kwstep,
            PyObject **defs, Py_ssize_t defcount,
            PyObject *kwdefs, PyObject *closure,
            PyObject *name, PyObject *qualname)
 {
     PyCodeObject* co = (PyCodeObject*)_co;
     PyFrameObject *f;
     PyObject *retval = NULL;
     PyThreadState *tstate;
     // 関係ないローカル変数は省略
 
     /* Create the frame */
     tstate = PyThreadState_GET();
     f = PyFrame_New(tstate, co, globals, locals);
 
     // 引数処理など
 
     retval = PyEval_EvalFrameEx(f,0);
 
 fail: /* Jump here from prelude on failure */
 
     /* decref'ing the frame can cause __del__ methods to get invoked,
        which can call back into Python.  While we're done with the
        current Python frame (f), the associated C stack is still in use,
        so recursion_depth must be boosted for the duration.
     */
     ++tstate->recursion_depth;
     Py_DECREF(f);
     --tstate->recursion_depth;
     return retval;
 }
 }}
 
 **PyFrame_New [#k7b9e41a]
 
 裏は取ってないけど意味を考えると最後のelseが実行されるのでしょうね。ともかくこれで空辞書がローカル変数としてセットされました。
 
 #code(C){{
 PyFrameObject *
 PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
             PyObject *locals)
 {
     // 省略
 
     /* Most functions have CO_NEWLOCALS and CO_OPTIMIZED set. */
     if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) == (CO_NEWLOCALS | CO_OPTIMIZED))
         ; /* f_locals = NULL; will be set by PyFrame_FastToLocals() */
     else if (code->co_flags & CO_NEWLOCALS) {
         locals = PyDict_New();
         if (locals == NULL) {
             Py_DECREF(f);
             return NULL;
         }
         f->f_locals = locals;
     }
     else {
         if (locals == NULL)
             locals = globals;
         Py_INCREF(locals);
         f->f_locals = locals;
     }
 
     // 省略
 }
 }}
 
 ところで、FrameObjectには実はローカル変数と称しているものが2つあります。今見たf_localsとf_localsplusです。前者は辞書、後者はよくあるローカル変数、つまり、スタック領域を使ったローカル変数のようです(もっともPythonの場合フレームは連続した領域に確保されるとは限らないようですが)。
 分けているのは名にし負わばにfast、高速化のためでしょうが今回は本筋ではないので詳しくは追いかけません。
 
 **STORE_NAME [#t18d1b10]
 
 ここで件のクラス定義コードを見てみましょう。
 
  >>> dis.dis(co.co_consts[0])
    2           0 LOAD_NAME                0 (__name__)
                2 STORE_NAME               1 (__module__)
                4 LOAD_CONST               0 ('Foo')
                6 STORE_NAME               2 (__qualname__)
  
    3           8 LOAD_CONST               1 (<code object __init__ at 0x03199A18, file "<string>", line 3>)
               10 LOAD_CONST               2 ('Foo.__init__')
               12 MAKE_FUNCTION            0
               14 STORE_NAME               3 (__init__)
  
    6          16 LOAD_CONST               3 (<code object method at 0x031992E0, file "<string>", line 6>)
               18 LOAD_CONST               4 ('Foo.method')
               20 MAKE_FUNCTION            0
               22 STORE_NAME               4 (method)
               24 LOAD_CONST               5 (None)
               26 RETURN_VALUE
 
 関数のコードオブジェクトと名前の文字列をスタックに積んで(LOAD_CONST)、MAKE_FUNCTIONして(ここで先に積んだ2オブジェクトが使われて1つの関数オブジェクトがスタックに積まれます)、STORE_NAMEと。見るべきなのはSTORE__NAME処理部分です。
 
 #code(C){{
         TARGET(STORE_NAME) {
             PyObject *name = GETITEM(names, oparg);
             PyObject *v = POP();
             PyObject *ns = f->f_locals;
             int err;
             if (PyDict_CheckExact(ns))
                 err = PyDict_SetItem(ns, name, v);
             else
                 err = PyObject_SetItem(ns, name, v);
             Py_DECREF(v);
             if (err != 0)
                 goto error;
             DISPATCH();
         }
 }}
 
 というわけで作られた関数オブジェクト(メソッド定義)がlocalsに設定されました。
 
 *クラス作成処理 [#u2746556]
 
 builtin___build_class__に戻って残りの処理です。
 
 #code(C){{
     if (cell != NULL) {
         PyObject *margs[3] = {name, bases, ns};
         cls = _PyObject_FastCallDict(meta, margs, 3, mkw);
         
         // 省略
     }
 error:
     Py_XDECREF(cell);
     Py_DECREF(ns);
     Py_DECREF(meta);
     Py_XDECREF(mkw);
     Py_DECREF(bases);
     return cls;
 }
 }}
 
 さきほども出てきた_PyObject_FastCallDictですが今回は対象のオブジェクトがType_Typeなので最後のelse部分が実行されます。_PyObject_FastCallDictはObjects/abstract.cに書かれています。
 
 #code(C){{
 PyObject *
 _PyObject_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs,
                        PyObject *kwargs)
 {
     ternaryfunc call;
     PyObject *result = NULL;
 
     if (Py_EnterRecursiveCall(" while calling a Python object")) {
         return NULL;
     }
 
     if (PyFunction_Check(func)) {
         result = _PyFunction_FastCallDict(func, args, nargs, kwargs);
     }
     else if (PyCFunction_Check(func)) {
         result = _PyCFunction_FastCallDict(func, args, nargs, kwargs);
     }
     else {
         PyObject *tuple;
 
         /* Slow-path: build a temporary tuple */
         call = func->ob_type->tp_call;
 
         tuple = _PyStack_AsTuple(args, nargs);
 
         result = (*call)(func, tuple, kwargs);
         Py_DECREF(tuple);
 
         result = _Py_CheckFunctionResult(func, result, NULL);
     }
 
 exit:
     Py_LeaveRecursiveCall();
 
     return result;
 }
 }}
 
 というわけでtp_callとして設定されているtype_callへ。あ、ちなみにcallというのは、foo()的に()をつけて呼び出した場合という意味です。これ後でも出てくるので覚えておいてください。
 
 **type_call (Objects/typeobject.c) [#gee9a82f]
 
 #code(C){{
 type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
 {
     PyObject *obj;
 
     obj = type->tp_new(type, args, kwds);
     obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
     if (obj == NULL)
         return NULL;
 
     /* Ugly exception: when the call was type(something),
        don't call tp_init on the result. */
     if (type == &PyType_Type &&
         PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
         (kwds == NULL ||
          (PyDict_Check(kwds) && PyDict_Size(kwds) == 0)))
         return obj;
 
     /* If the returned object is not an instance of type,
        it won't be initialized. */
     if (!PyType_IsSubtype(Py_TYPE(obj), type))
         return obj;
 
     type = Py_TYPE(obj);
     if (type->tp_init != NULL) {
         int res = type->tp_init(obj, args, kwds);
         if (res < 0) {
             assert(PyErr_Occurred());
             Py_DECREF(obj);
             obj = NULL;
         }
         else {
             assert(!PyErr_Occurred());
         }
     }
     return obj;
 }
 }}
 
 本筋ではないですが真ん中あたりでUglyと言われている部分について。
 この部分はPythonでtype関数を呼び出した時の処理です。type関数は「type('foo')」のように呼び出すと'foo'のクラスオブジェクトを返します。ここではその処理を行っています。
 
 **type_new (Objects/typeobject.c) [#id807c38]
 
 tp_newが指しているtype_newへ。type_newは500行近くあるので要点だけ。と言っても長いですが。
 
 #code(C){{
 static PyObject *
 type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
 {
     PyObject *name, *bases = NULL, *orig_dict, *dict = NULL;
     PyObject *qualname, *slots = NULL, *tmp, *newslots, *cell;
     PyTypeObject *type = NULL, *base, *tmptype, *winner;
     PyHeapTypeObject *et;
     PyMemberDef *mp;
     Py_ssize_t i, nbases, nslots, slotoffset, name_size;
     int j, may_add_dict, may_add_weak, add_dict, add_weak;
     _Py_IDENTIFIER(__qualname__);
     _Py_IDENTIFIER(__slots__);
     _Py_IDENTIFIER(__classcell__);
 
     /* Special case: type(x) should return x->ob_type */
     // 省略
 
     /* Check arguments: (name, bases, dict) */
     if (!PyArg_ParseTuple(args, "UO!O!:type.__new__", &name, &PyTuple_Type,
                           &bases, &PyDict_Type, &orig_dict))
         return NULL;
 
     // 省略
 
     dict = PyDict_Copy(orig_dict);
 
     // 省略
     /* Check for a __slots__ sequence variable in dict, and count it */
     slots = _PyDict_GetItemId(dict, &PyId___slots__);
     nslots = 0;
     add_dict = 0;
     add_weak = 0;
     may_add_dict = base->tp_dictoffset == 0;
     may_add_weak = base->tp_weaklistoffset == 0 && base->tp_itemsize == 0;
     if (slots == NULL) {
         // 今回はこっち
         // may_add_dictもmay_add_weakも真
         if (may_add_dict) {
             add_dict++;
         }
         if (may_add_weak) {
             add_weak++;
         }
     }
     else {
         // 省略
     }
 
     /* Allocate the type object */
     type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);
 
     // 省略
 
     /* Initialize tp_dict from passed-in dict */
     Py_INCREF(dict);
     type->tp_dict = dict;
 
     // 省略
 
     if (type->tp_weaklistoffset && type->tp_dictoffset)
         type->tp_getset = subtype_getsets_full;
     else if (type->tp_weaklistoffset && !type->tp_dictoffset)
         type->tp_getset = subtype_getsets_weakref_only;
     else if (!type->tp_weaklistoffset && type->tp_dictoffset)
         type->tp_getset = subtype_getsets_dict_only;
     else
         type->tp_getset = NULL;
     /* Add descriptors for custom slots from __slots__, or for __dict__ */
     mp = PyHeapType_GET_MEMBERS(et);
     slotoffset = base->tp_basicsize;
     if (et->ht_slots != NULL) {
         // 省略
     }
     if (add_dict) {
         if (base->tp_itemsize)
             type->tp_dictoffset = -(long)sizeof(PyObject *);
         else
             type->tp_dictoffset = slotoffset;
         slotoffset += sizeof(PyObject *);
     }
     if (add_weak) {
         assert(!base->tp_itemsize);
         type->tp_weaklistoffset = slotoffset;
         slotoffset += sizeof(PyObject *);
     }
     type->tp_basicsize = slotoffset;
     type->tp_itemsize = base->tp_itemsize;
     type->tp_members = PyHeapType_GET_MEMBERS(et);
 
     // 省略
 
     /* Initialize the rest */
     if (PyType_Ready(type) < 0)
         goto error;
 
     // 省略
 
     Py_DECREF(dict);
     return (PyObject *)type;
 }
 }}
 
 クラス定義コードを実行して作成した属性辞書が設定されました。
 後、インスタンスが属性アクセスする際に関係ありそうなtp_getsetとPyType_Readyが呼ばれているんだなということを確認しておきます。
 後、インスタンスが属性アクセスする際に関係ありそうなdictoffsetの設定とPyType_Readyが呼ばれているんだなということを確認しておきます。
 
 type_callに戻った後tp_initが呼ばれていますが今回の場合は目立った処理はしてないので省略。また後で出てきます。
 
 *おわりに [#yb88015f]
 
 今回はbuiltin___build_class__の残りの部分について見てきました。Pythonのクラス定義にはぶっちゃけなんでも書けます。そのからくりは「そもそも普通に関数実行してるだけだし」ということでした。「デコレータってつまり関数呼び出しなわけだけどなんでこういう風に書けるの?」と思ってましたが本当に関数実行していたわけですね(笑)
 
 ともかくクラス定義コードを実行して得られた属性辞書(メソッド名: 関数オブジェクト)をType_Typeのtp_callを呼び出してクラスオブジェクトを作成します。さっくり省略していますがまあそこは実際に使われている箇所と照らし合わせてみないとわかりにくいので。というわけで続いてインスタンス生成について見ていきましょう。

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