Pythonを読む

はじめに

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

Everything is expanded.Everything is shortened.
  1
 
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)

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

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 
 
-
|
|
|
|
|
|
|
-
|
!
-
-
!
-
-
|
|
|
|
|
|
|
|
|
!
-
|
!
|
|
|
-
|
!
-
|
!
|
|
!
 
-
!
 
!
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が呼び出されます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 
 
-
|
|
|
-
|
!
|
-
|
!
|
-
|
-
|
!
!
-
|
!
|
|
|
|
!
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再び

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

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

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
 
 
-
|
|
!
    /* 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);
    }

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

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
 
 
 
-
|
|
|
!
    /* 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へ。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 
 
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
!
|
|
|
-
|
|
!
-
|
|
!
|
|
|
|
|
-
!
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)

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

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

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

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

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

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
 
 
 
 
 
 
    /* Initialize the rest */
    if (PyType_Ready(type) < 0)
        goto error;
 
    /* Put the proper slots in place */
    fixup_slot_dispatchers(type);
Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
-
|
!
 
 
-
|
|
|
|
|
!
/* 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はこんな感じです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 
 
 
-
!
 
-
-
!
|
|
|
|
-
!
|
!
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レベルで特殊メソッドが定義されていたらそっちを呼び出す関数に入れ替える」ということをしているようです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 
 
-
|
|
|
|
|
|
|
-
-
|
!
|
!
-
|
-
-
|
!
|
!
|
-
-
!
|
|
|
|
-
-
!
 
-
-
!
-
 
 
!
!
|
|
|
|
|
!
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)

というわけでslot_tp_initです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 
-
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
!
|
|
!
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を設定している箇所がなかったので別方面から検索してみたところなかなか巧妙なことが行われていることがわかりました。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 
-
|
|
|
|
!
 
 
 
-
|
|
|
-
|
|
|
|
|
!
|
!
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)

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 
 
 
-
-
|
|
!
|
!
/* 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実装関数のオブジェクトです。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 
-
|
-
|
|
!
|
-
|
|
|
!
-
|
|
|
!
|
|
|
|
|
|
|
!
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)

method_call

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 
 
-
|
|
|
|
|
|
!
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

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

Everything is expanded.Everything is shortened.
  1
 
        self.x = x

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

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

で、STORE_ATTRの処理部分

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
-
|
|
|
|
|
|
|
|
|
|
|
!
        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に移動。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 
 
-
|
|
|
|
|
|
-
|
|
|
!
-
!
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に設定されています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 
 
-
|
!
 
 
 
 
-
|
|
|
|
|
|
-
|
|
|
|
!
|
|
|
|
|
|
|
|
-
-
!
 
-
|
|
!
-
-
!
|
|
|
|
|
|
|
!
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がそのオブジェクトについて初めて呼ばれたときに作成されます。共有を考慮して作られるようですが、もうかなり長くなっているのでここまでとします。

おわりに

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

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

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


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2018-01-01 (月) 17:47:49 (348d)