美文网首页
大师兄的Python源码学习笔记(二十六): 虚拟机中的类机制(

大师兄的Python源码学习笔记(二十六): 虚拟机中的类机制(

作者: superkmi | 来源:发表于2021-07-30 15:30 被阅读0次

大师兄的Python源码学习笔记(二十五): 虚拟机中的类机制(四)
大师兄的Python源码学习笔记(二十七): 虚拟机中的类机制(六)

三. 用户自定义Class

2. 从class对象到instance对象
  • 在虚拟机执行时,在内存中运行的是一个个instance对象而不是class对象
demo.py 

a = A()
 13          16 LOAD_NAME                1 (A)
             18 CALL_FUNCTION            0
             20 STORE_NAME               2 (a)
  • 从这段字节码可以看出,虚拟机通过执行CALL_FUNCTION,调用class对象A,创建了instance对象a。
Python\ceval.c

        TARGET(CALL_FUNCTION_EX) {
            PyObject *func, *callargs, *kwargs = NULL, *result;
            ... ...
            result = do_call_core(func, callargs, kwargs);
            ... ...
          }
Python\ceval.c

static PyObject *
do_call_core(PyObject *func, PyObject *callargs, PyObject *kwdict)
{
    if (PyCFunction_Check(func)) {
        PyObject *result;
        PyThreadState *tstate = PyThreadState_GET();
        C_TRACE(result, PyCFunction_Call(func, callargs, kwdict));
        return result;
    }
    else {
        return PyObject_Call(func, callargs, kwdict);
    }
}
Python\ceval.c

PyObject *
PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs)
{
    ternaryfunc call;
    PyObject *result;

    /* PyObject_Call() must not be called with an exception set,
       because it can clear it (directly or indirectly) and so the
       caller loses its exception */
    assert(!PyErr_Occurred());
    assert(PyTuple_Check(args));
    assert(kwargs == NULL || PyDict_Check(kwargs));

    if (PyFunction_Check(callable)) {
        return _PyFunction_FastCallDict(callable,
                                        &PyTuple_GET_ITEM(args, 0),
                                        PyTuple_GET_SIZE(args),
                                        kwargs);
    }
    else if (PyCFunction_Check(callable)) {
        return PyCFunction_Call(callable, args, kwargs);
    }
    else {
        call = callable->ob_type->tp_call;
        if (call == NULL) {
            PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
                         callable->ob_type->tp_name);
            return NULL;
        }

        if (Py_EnterRecursiveCall(" while calling a Python object"))
            return NULL;

        result = (*call)(callable, args, kwargs);

        Py_LeaveRecursiveCall();

        return _Py_CheckFunctionResult(callable, result, NULL);
    }
}
  • 虚拟机一路从CALL_FUNCTION_EX开始,经过PyObject_Call,最终进入PyObject_Call
  • 到这里,虚拟机会寻找class对象<class A>的type中定义的tp_call操作。
Objects\typeobject.c

static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj;

    ... ...
    obj = type->tp_new(type, args, kwds);
    ... ...
    return obj;
}
  • 执行对象的tp_call时,将调用对象的tp_new创建instance对象
  • 在创建class对象<class A>时,虚拟机调用了PyType_Ready对<class A>进行初始化,其中继承了基类的操作。
  • 所以A.tp_new实际上也是object.tp_new。
Objects\typeobject.c

PyTypeObject PyBaseObject_Type = {
    ... ...
    object_new,                                 /* tp_new */
    ... ...
};
  • PyBaseObject_Type对应的tp_new方法为object_new
  • 所以创建class对象和创建instnace对象的不同之处是在于tp_new不同,创建class对象时,虚拟机使用的是type_new,而对于instance对象则使用object_new
Objects\typeobject.c

static PyObject *
object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    ... ...
    return type->tp_alloc(type, 0);
}
  • object_new中调用了tp_alloc,也是继承自object,调用了PyType_GenericAlloc
Objects\typeobject.c

PyTypeObject PyBaseObject_Type = {
    ... ...
    PyType_GenericAlloc,                        /* tp_alloc */
    ... ...
};
  • PyType_GenericAlloc将申请24字节的内存空间。
A.tp_basicsize = PyBaseObject_Type.tp_basicsize + 8 = sizeof(PyObject) + 8 = 24
A.tp_itemsize = PyBaseObject_Type.tp_itemsize = 0
Objects\typeobject.c

PyObject *
PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
{
    PyObject *obj;
    const size_t size = _PyObject_VAR_SIZE(type, nitems+1);
    /* note that we need to add one, for the sentinel */

    if (PyType_IS_GC(type))
        obj = _PyObject_GC_Malloc(size);
    else
        obj = (PyObject *)PyObject_MALLOC(size);

    if (obj == NULL)
        return PyErr_NoMemory();

    memset(obj, '\0', size);

    if (type->tp_flags & Py_TPFLAGS_HEAPTYPE)
        Py_INCREF(type);

    if (type->tp_itemsize == 0)
        (void)PyObject_INIT(obj, type);
    else
        (void) PyObject_INIT_VAR((PyVarObject *)obj, type, nitems);

    if (PyType_IS_GC(type))
        _PyObject_GC_TRACK(obj);
    return obj;
}
  • 所以object_new实际只是申请了24个字节的内存空间。
  • 申请空间后回到tp_call,会尝试进行初始化:
Objects\typeobject.c

static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj;

    ... ...

    obj = type->tp_new(type, args, kwds);
    ... ...

    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;
}
Include\object.h

#define Py_TYPE(ob)             (((PyObject*)(ob))->ob_type)
  • 这里tp_initPyType_Ready时会继承PyBaseObject_Typeobject_init操作。
Objects\typeobject.c

PyTypeObject PyBaseObject_Type = {
    ... ...
    object_init,                                /* tp_init */
    ... ...
};
  • 但由于A中重写了__init__,所以在fixup_slot_dispatchers中,tp_init会指向slotdefs中指定的与__init__对应的slot_tp_init
demo.py
 
class A(object):
    name = 'A'
    def ___init__(self):
        print('A::__init__')
    ... ...
Objects\typeobject.c

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

    if (meth == NULL)
        return -1;
    if (unbound) {
        res = _PyObject_Call_Prepend(meth, self, args, kwds);
    }
    else {
        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_methodclass对象及其mro列表中所搜属性__init__对应的操作,然后通过PyObject_Call调用该操作。
  • 如果没有重写__init__,那么最终结果将调用object_init
Objects\typeobject.c

static int
object_init(PyObject *self, PyObject *args, PyObject *kwds)
{
    PyTypeObject *type = Py_TYPE(self);
    if (excess_args(args, kwds)) {
        if (type->tp_init != object_init) {
            PyErr_SetString(PyExc_TypeError, "object.__init__() takes no arguments");
            return -1;
        }
        if (type->tp_new == object_new) {
            PyErr_Format(PyExc_TypeError, "%.200s().__init__() takes no arguments",
                         type->tp_name);
            return -1;
        }
    }
    return 0;
}
  • object_init中,虚拟机几乎什么也没做,直接返回0。
  • 所以当创建instance对象时,实际上是没有进行任何初始化动作的。
  • class对象创建instance对象的源码部分与Python一致,需要经过两个魔法函数:
class.__new__(class, args, kwds)
class.__init__(instance, args, kwds)

相关文章

网友评论

      本文标题:大师兄的Python源码学习笔记(二十六): 虚拟机中的类机制(

      本文链接:https://www.haomeiwen.com/subject/sefgmltx.html