[[Pythonを読む]] #contents *はじめに [#s49b9b76] 最後、メソッド呼び出しです。もうここまで来たら大体わかったような気もしますが見ていきましょう。対象のPythonコード #code(Python){{ foo.method() }} 対応するバイトコード 10 22 LOAD_NAME 1 (foo) 24 LOAD_ATTR 2 (method) 26 CALL_FUNCTION 0 *LOAD_ATTR [#k1ae9d7d] というわけでとっかかりはLOAD_ATTRの処理部分です。 #code(C){{ TARGET(LOAD_ATTR) { PyObject *name = GETITEM(names, oparg); PyObject *owner = TOP(); PyObject *res = PyObject_GetAttr(owner, name); Py_DECREF(owner); SET_TOP(res); if (res == NULL) goto error; DISPATCH(); } }} STORE_ATTRがSetAttr呼び出していたのに対応する形でGetAttrですね。 #code(C){{ PyObject * PyObject_GetAttr(PyObject *v, PyObject *name) { PyTypeObject *tp = Py_TYPE(v); if (tp->tp_getattro != NULL) return (*tp->tp_getattro)(v, name); // 省略 } }} tp_getattroはPyObject_GenericGetAttrです。 #code(C){{ PyObject * PyObject_GenericGetAttr(PyObject *obj, PyObject *name) { return _PyObject_GenericGetAttrWithDict(obj, name, NULL); } PyObject * _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name, PyObject *dict) { PyTypeObject *tp = Py_TYPE(obj); PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; Py_ssize_t dictoffset; PyObject **dictptr; Py_INCREF(name); if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } descr = _PyType_Lookup(tp, name); f = NULL; if (descr != NULL) { Py_INCREF(descr); f = descr->ob_type->tp_descr_get; if (f != NULL && PyDescr_IsData(descr)) { res = f(descr, obj, (PyObject *)obj->ob_type); goto done; } } if (dict == NULL) { /* Inline _PyObject_GetDictPtr */ // 省略 } if (dict != NULL) { // 省略 } if (f != NULL) { res = f(descr, obj, (PyObject *)Py_TYPE(obj)); goto done; } if (descr != NULL) { res = descr; descr = NULL; goto done; } PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%U'", tp->tp_name, name); done: Py_XDECREF(descr); Py_DECREF(name); return res; } }} 今対象にしている「method」はクラスオブジェクトの方の辞書に入っています。また、FunctionObjectなのでtp_descr_getにはfunc_descr_getが設定されています。func_descr_getは前回も見たようにselfをバインドしたMethodObjectを返します。てことはLOAD_ATTRするたびにMethodObjectが作られて、ああ、だからfree_listを使ってオブジェクトの再利用をしているわけか。ともかくこれでselfをバインドしたMethodObjectがスタックに積まれました。 *call_function (Python/ceval.c) [#zecde35b] さてメソッド呼び出しです。バイトコードではメソッドの場合でも命令はCALL_FUNCTIONです。前回も見ましたがcall_function #code(C){{ static PyObject * call_function(PyObject ***pp_stack, Py_ssize_t oparg, PyObject *kwnames) { PyObject **pfunc = (*pp_stack) - oparg - 1; PyObject *func = *pfunc; PyObject *x, *w; Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames); Py_ssize_t nargs = oparg - nkwargs; PyObject **stack; /* Always dispatch PyCFunction first, because these are presumed to be the most frequent callable object. */ if (PyCFunction_Check(func)) { // こっちじゃない } else { if (PyMethod_Check(func) && PyMethod_GET_SELF(func) != NULL) { /* optimize access to bound methods */ PyObject *self = PyMethod_GET_SELF(func); PCALL(PCALL_METHOD); PCALL(PCALL_BOUND_METHOD); Py_INCREF(self); func = PyMethod_GET_FUNCTION(func); Py_INCREF(func); Py_SETREF(*pfunc, self); nargs++; } else { Py_INCREF(func); } stack = (*pp_stack) - nargs - nkwargs; if (PyFunction_Check(func)) { x = fast_function(func, stack, nargs, kwnames); } else { x = _PyObject_FastCallKeywords(func, stack, nargs, kwnames); } Py_DECREF(func); } // 省略 return x; } }} funcがMethodObjectの場合、 +MethodObjectからFunctionObjectを取り出してfuncに設定 +*pfuncにselfを設定 +引数の数(nargs)を1足す ということをしています。特に2つ目が巧妙で、 pfuncはポインタのポインタであり、初期状態では呼び出すオブジェクトを指す。つまり、 **pp_stack→ 引数 ※今の場合は引数はないです *pfunc→呼び出すオブジェクト ということになっています。これが上で挙げた2つ目の処理により、 **pp_stack→ 引数 ※今の場合は引数はないです *pfunc→self となります。これによりselfを引数として渡すということが実現できました。ここまで読めば残りはもういいでしょう。 *おわりに [#l43493cf] 今回はメソッド呼び出しについて見てきました。Pythonはselfを特別扱いしない言語(あくまでただの引数)ですがその裏では手の込んだ処理をしてるなという印象です。 これにて「クラス定義とインスタンス生成、メソッド呼び出し」に関する読解は終了です。軽くまとめておきましょう。 -クラス定義とはクラス定義コードの実行(関数実行)である。その目的は属性辞書(メソッド定義、クラス変数)を作ることである。なおこの時点では正確にはメソッドではなく関数オブジェクトである(インスタンスに紐づいていないため) -作成された属性辞書を使ってPyTypeObjectを作る。PyTypeObjectがPythonレベルのクラスである。 -PyTypeObjectには特殊メソッドに対応するC言語の実装を設定できる。callに対応する関数でインスタンスを生成する。 -関数オブジェクトは辞書から取り出されるときにselfがバインドされメソッドオブジェクトになる。これにより第1引数がselfになる。これにはデスクリプタが利用されている。 -PyObjectには直接はインスタンス辞書のメンバはない。実際にはメモリを少し多めにとってdictoffsetで示す位置に辞書オブジェクトへのポインタが設定される。