[[Pythonを読む]]

#contents

*はじめに [#g26b6764]

PyObjectから入ったので長くなりましたがようやくクラスが作成されました。今回はそのクラスのインスタンスを生成する処理について見ていきます。具体的には、

#code(Python){{
foo = Foo(123)
}}

対応するバイトコードこんな感じ。

   9          14 LOAD_NAME                0 (Foo)
              16 LOAD_CONST               2 (123)
              18 CALL_FUNCTION            1
              20 STORE_NAME               1 (foo)

*call_function (Python/ceval.c) [#e34fe680]

というわけでCALL_FUNCTIONに対応するcall_functionです。前にも見ましたが今回は今回はfuncの実体がC関数ではないのでelseの方が実行されます。

#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;
}
}}

メソッド呼び出し用の加工処理っぽいことしてますがそれはまた後で見るとして、_PyObject_FastCallKeywordsが呼び出されます。

#code(C){{
PyObject *
_PyObject_FastCallKeywords(PyObject *func, PyObject **stack, Py_ssize_t nargs,
                           PyObject *kwnames)
{
    PyObject *kwdict, *result;
    Py_ssize_t nkwargs = (kwnames == NULL) ? 0 : PyTuple_GET_SIZE(kwnames);

    if (PyFunction_Check(func)) {
        return _PyFunction_FastCallKeywords(func, stack, nargs, kwnames);
    }

    if (PyCFunction_Check(func)) {
        return _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames);
    }

    if (nkwargs > 0) {
        kwdict = _PyStack_AsDict(stack + nargs, kwnames);
        if (kwdict == NULL) {
            return NULL;
        }
    }
    else {
        kwdict = NULL;
    }

    result = _PyObject_FastCallDict(func, stack, nargs, kwdict);
    Py_XDECREF(kwdict);
    return result;
}
}}

関数オブジェクトでなく、Cで実装された関数でもないので_PyObject_FastCallDictが呼び出されます。

*PyType_Ready再び [#gd4a7722]

さてというわけで_PyObject_FastCallDictが呼び出されてその中でPyObjectのPyTypeObjectのtp_callが呼ばれるわけですが今回はどの関数が呼ばれるのでしょうか。前はdict周りを見ましたが今回はtp_callがどう設定されるのかを見てみましょう。

まずは前提として、親クラスは指定していないので以下のif文が実行されBaseObjectが親クラスになります。

#code(C){{
    /* Initialize tp_base (defaults to BaseObject unless that's us) */
    base = type->tp_base;
    if (base == NULL && type != &PyBaseObject_Type) {
        base = type->tp_base = &PyBaseObject_Type;
        Py_INCREF(base);
    }
}}

その後読み進めていくと以下の部分があります。

#code(C){{
    /* Initialize tp_dict properly */
    bases = type->tp_mro;
    n = PyTuple_GET_SIZE(bases);
    for (i = 1; i < n; i++) {
        PyObject *b = PyTuple_GET_ITEM(bases, i);
        if (PyType_Check(b))
            inherit_slots(type, (PyTypeObject *)b);
    }
}}

inherit_slotsへ。

#code(C){{
static void
inherit_slots(PyTypeObject *type, PyTypeObject *base)
{
    PyTypeObject *basebase;

#undef SLOTDEFINED
#undef COPYSLOT
#undef COPYNUM
#undef COPYSEQ
#undef COPYMAP
#undef COPYBUF

#define SLOTDEFINED(SLOT) \
    (base->SLOT != 0 && \
     (basebase == NULL || base->SLOT != basebase->SLOT))

#define COPYSLOT(SLOT) \
    if (!type->SLOT && SLOTDEFINED(SLOT)) type->SLOT = base->SLOT

    // 省略

    basebase = base->tp_base;

    COPYSLOT(tp_dealloc);
    if (type->tp_getattr == NULL && type->tp_getattro == NULL) {
        type->tp_getattr = base->tp_getattr;
        type->tp_getattro = base->tp_getattro;
    }
    if (type->tp_setattr == NULL && type->tp_setattro == NULL) {
        type->tp_setattr = base->tp_setattr;
        type->tp_setattro = base->tp_setattro;
    }
    /* tp_reserved is ignored */
    COPYSLOT(tp_repr);
    /* tp_hash see tp_richcompare */
    COPYSLOT(tp_call);
    
    // 省略
}
}}

というわけで、新しく作ったクラスのtp_callはBaseObjectのtp_callが使われます。
と見に行ったらBaseObjectのtp_callもNULLでした。BaseObjectはTypeが親クラスなので結局type_callが使われるということになります。

*fixup_slot_dispatchers (Objects/typeobject.c) [#b4e915d8]

type_callは前回も見ました。大雑把に言うと、

+tp_newを呼び出してオブジェクトを作って
+tp_initで初期化

を行います。ここで、今回はそれぞれobject_new、object_initが呼び出されるということになります。

・・・と思ったのですが、object_init見ても__init__メソッド呼んでないので、それっぽいのを探してみるとslot_tp_initがそれっぽいですね。

前回は省略してましたが、type_newの中でPyType_Readyの後でfixup_slot_dispatchersが呼ばれていました。

#code(C){{
    /* Initialize the rest */
    if (PyType_Ready(type) < 0)
        goto error;

    /* Put the proper slots in place */
    fixup_slot_dispatchers(type);
}}

#code(C){{
/* Store the proper functions in the slot dispatches at class (type)
   definition time, based upon which operations the class overrides in its
   dict. */
static void
fixup_slot_dispatchers(PyTypeObject *type)
{
    slotdef *p;

    init_slotdefs();
    for (p = slotdefs; p->name; )
        p = update_one_slot(type, p);
}
}}

slotdefsはこんな感じです。

#code(C){{
typedef struct wrapperbase slotdef;

#define FLSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC, FLAGS) \
    {NAME, offsetof(PyTypeObject, SLOT), (void *)(FUNCTION), WRAPPER, \
     PyDoc_STR(DOC), FLAGS}

static slotdef slotdefs[] = {
    // 省略

    FLSLOT("__call__", tp_call, slot_tp_call, (wrapperfunc)wrap_call,
           "__call__($self, /, *args, **kwargs)\n--\n\nCall self as a function.",
           PyWrapperFlag_KEYWORDS),

    // 省略

    {NULL}
};
}}

update_one_slotはちょっとややこしい。要約すると、「Pythonレベルで特殊メソッドが定義されていたらそっちを呼び出す関数に入れ替える」ということをしているようです。

#code(C){{
static slotdef *
update_one_slot(PyTypeObject *type, slotdef *p)
{
    PyObject *descr;
    PyWrapperDescrObject *d;
    void *generic = NULL, *specific = NULL;
    int use_generic = 0;
    int offset = p->offset;
    void **ptr = slotptr(type, offset);

    if (ptr == NULL) {
        do {
            ++p;
        } while (p->offset == offset);
        return p;
    }
    do {
        descr = _PyType_Lookup(type, p->name_strobj);
        if (descr == NULL) {
            if (ptr == (void**)&type->tp_iternext) {
                specific = (void *)_PyObject_NextNotImplemented;
            }
            continue;
        }
        if (Py_TYPE(descr) == &PyWrapperDescr_Type &&
            ((PyWrapperDescrObject *)descr)->d_base->name_strobj == p->name_strobj) {
            // 省略
        }
        else if (Py_TYPE(descr) == &PyCFunction_Type &&
                 PyCFunction_GET_FUNCTION(descr) ==
                 (PyCFunction)tp_new_wrapper &&
                 ptr == (void**)&type->tp_new)
        {
            // 省略
        }
        else if (descr == Py_None &&
                 ptr == (void**)&type->tp_hash) {
            // 省略
        }
        else {
            use_generic = 1;
            generic = p->function;
        }
    } while ((++p)->offset == offset);
    if (specific && !use_generic)
        *ptr = specific;
    else
        *ptr = generic;
    return p;
}
}}

*slot_tp_init (Objects/typeobject.c) [#c37bb761]

というわけでslot_tp_initです。

#code(C){{
static int
slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    _Py_IDENTIFIER(__init__);
    PyObject *meth = lookup_method(self, &PyId___init__);
    PyObject *res;

    if (meth == NULL)
        return -1;
    res = PyObject_Call(meth, args, kwds);
    Py_DECREF(meth);
    if (res == NULL)
        return -1;
    if (res != Py_None) {
        PyErr_Format(PyExc_TypeError,
                     "__init__() should return None, not '%.200s'",
                     Py_TYPE(res)->tp_name);
        Py_DECREF(res);
        return -1;
    }
    Py_DECREF(res);
    return 0;
}
}}

lookup_method、初めはよくわからなかったのですがどうにもselfを設定している箇所がなかったので別方面から検索してみたところなかなか巧妙なことが行われていることがわかりました。

#code(C){{
static PyObject *
lookup_method(PyObject *self, _Py_Identifier *attrid)
{
    PyObject *res = lookup_maybe(self, attrid);
    if (res == NULL && !PyErr_Occurred())
        PyErr_SetObject(PyExc_AttributeError, attrid->object);
    return res;
}

static PyObject *
lookup_maybe(PyObject *self, _Py_Identifier *attrid)
{
    PyObject *res;

    res = _PyType_LookupId(Py_TYPE(self), attrid);
    if (res != NULL) {
        descrgetfunc f;
        if ((f = Py_TYPE(res)->tp_descr_get) == NULL)
            Py_INCREF(res);
        else
            res = f(res, self, (PyObject *)(Py_TYPE(self)));
    }
    return res;
}
}}

resはFunctionObjectです。そのtp_descr_getはfunc_descr_getです。

**func_descr_get (Objects/funcobject.c) [#reb6abcf]

#code(C){{
/* Bind a function to an object */
static PyObject *
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
{
    if (obj == Py_None || obj == NULL) {
        Py_INCREF(func);
        return func;
    }
    return PyMethod_New(func, obj);
}
}}

PyMethod_NewはObjects/classobject.cに書かれています。ややこしいですがmethodobject.cはC実装関数のオブジェクトです。

#code(C){{
PyObject *
PyMethod_New(PyObject *func, PyObject *self)
{
    PyMethodObject *im;
    if (self == NULL) {
        PyErr_BadInternalCall();
        return NULL;
    }
    im = free_list;
    if (im != NULL) {
        free_list = (PyMethodObject *)(im->im_self);
        (void)PyObject_INIT(im, &PyMethod_Type);
        numfree--;
    }
    else {
        im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);
        if (im == NULL)
            return NULL;
    }
    im->im_weakreflist = NULL;
    Py_INCREF(func);
    im->im_func = func;
    Py_XINCREF(self);
    im->im_self = self;
    _PyObject_GC_TRACK(im);
    return (PyObject *)im;
}
}}

self設定されていますね。というわけで、lookup_methodによりただの関数オブジェクトだったものにselfがバインドされてメソッドオブジェクトになりました。

*method_call (Objects/classobject.c) [#lcd11bd8]

method_call

#code(C){{
static PyObject *
method_call(PyObject *method, PyObject *args, PyObject *kwargs)
{
    PyObject *self, *func;

    self = PyMethod_GET_SELF(method);
    func = PyMethod_GET_FUNCTION(method);

    return _PyObject_Call_Prepend(func, self, args, kwargs);
}
}}

_PyObject_Call_PrependはObjects/abstract.cに書かれていて、期待通りにselfをargsの先頭に付加、_PyObject_FastCallDictを呼び出します。(見ればわかるのでコードは省略)

_PyObject_FastCallDictは同じくabstract.cに書かれていて、今の場合はfuncが関数オブジェクトなので_PyFunction_FastCallDictが呼び出されます。

_PyFunction_FastCallDictはceval.cです。今の場合は_PyFunction_FastCallに行き、PyEval_EvalFrameExに進みます。つまり、__init__メソッドが呼び出されます。

*STORE_ATTR [#l8570877]

最後に属性を設定しているところを確認しましょう。すなわち、Pythonコードだと、

#code(Python){{
        self.x = x
}}

対応するバイトコードは、

   4           0 LOAD_FAST                1 (x)
               2 LOAD_FAST                0 (self)
               4 STORE_ATTR               0 (x)

で、STORE_ATTRの処理部分

#code(C){{
        TARGET(STORE_ATTR) {
            PyObject *name = GETITEM(names, oparg);
            PyObject *owner = TOP();
            PyObject *v = SECOND();
            int err;
            STACKADJ(-2);
            err = PyObject_SetAttr(owner, name, v);
            Py_DECREF(v);
            Py_DECREF(owner);
            if (err != 0)
                goto error;
            DISPATCH();
        }
}}

Objects/object.cに移動。

#code(C){{
int
PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
{
    PyTypeObject *tp = Py_TYPE(v);
    int err;

    Py_INCREF(name);

    PyUnicode_InternInPlace(&name);
    if (tp->tp_setattro != NULL) {
        err = (*tp->tp_setattro)(v, name, value);
        Py_DECREF(name);
        return err;
    }
    // 省略
}
}}

tp_setattroはPyObject_GenericSetAttrに設定されています。

#code(C){{
int
PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value)
{
    return _PyObject_GenericSetAttrWithDict(obj, name, value, NULL);
}

int
_PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
                                 PyObject *value, PyObject *dict)
{
    PyTypeObject *tp = Py_TYPE(obj);
    PyObject *descr;
    descrsetfunc f;
    PyObject **dictptr;
    int res = -1;

    if (!PyUnicode_Check(name)){
        PyErr_Format(PyExc_TypeError,
                     "attribute name must be string, not '%.200s'",
                     name->ob_type->tp_name);
        return -1;
    }

    if (tp->tp_dict == NULL && PyType_Ready(tp) < 0)
        return -1;

    Py_INCREF(name);

    descr = _PyType_Lookup(tp, name);

    if (descr != NULL) {
        // 省略
    }

    if (dict == NULL) {
        dictptr = _PyObject_GetDictPtr(obj);
        res = _PyObjectDict_SetItem(tp, dictptr, name, value);
    }
    else {
        // 省略
    }
    if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
        PyErr_SetObject(PyExc_AttributeError, name);

  done:
    Py_XDECREF(descr);
    Py_DECREF(name);
    return res;
}
}}

_PyObject_GetDictPtrはdictoffsetで指定された位置を返します。
なおこの時点ではまだオブジェクトに属性辞書は紐づいていません。_PyObjectDict_SetItemがそのオブジェクトについて初めて呼ばれたときに作成されます。共有を考慮して作られるようですが、もうかなり長くなっているのでここまでとします。

*おわりに [#eccb50f9]

今回はインスタンスの作成について見てきました。

-メソッドの継承
-継承と言いつつ子クラスでオーバーライドされているものがあるので動作が少し変わる
-さらにCレベルでのオーバーライドとPythonレベルでのオーバーライド(を考慮したCでの呼び出し調整)があるのでより話がややこしくなる
-いつの間にselfがバインドされたんだ?の場所探し
-インスタンス辞書も一筋縄ではいかない

となかなか盛沢山でしたね。特にインスタンス辞書は素直にPyObjectにdictを入れてしまっていいように思うのですが、なんか理由あるのかな。


トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS