美文网首页
【Python】虚拟机中的类机制

【Python】虚拟机中的类机制

作者: lndyzwdxhs | 来源:发表于2018-12-16 17:00 被阅读17次

    Python 2.2开始,Python中有两套类机制

    • classic class(经典类)
    • new style class(新式类)

    0x01 Python中的对象模型

    Python 2.2之前,Python内置的typeintdict等)与程序员自己定义的class并不是完全等同的。

    • 用户自定义的类可以被继承,python内置type不能被继承。

    Python 2.2之后的版本,填补了内置type和用户自定义class之间的问题,使两者在概念上完全一致,这种统一以后的类型机制就叫做new style class(新式类)

    统一术语:
    type对象:Python内置的类型
    class对象:Python程序员定义的类型(<class A>
    instance对象(实例对象): 由class对象创建的实例(<instance a>

    Python 2.2之后,typeclass已经统一,所以我们用"class对象"来统一的表示Python 2.2之前的"type对象"和"class对象"。

    对象间的关系

    Python的三种对象之间,存在两种关系

    • is-kind-of关系:面向对象中的基类与子类之间的关系(使用对象的__base__属性或issubclass来探测关系),并不是所有对象都有这种关系(instance对象就没有)
    • is-instance-of关系:面相对象中类与实例之间的关系(使用对象的__class__属性或内置的type函数或isinstanceof函数来探测关系),所有对象都有这种关系

    <type 'type'>和<type 'object'>

    <type 'type'>属于Python中的一种特殊的class对象,这种特殊的class对象能够成为其他class对象的type,这种特殊的class我们成为metaclass对象

    Python中还有另外一个特殊的class对象<type 'object'>,任何一个class都必须直接或者间接继承自object(万物之母)。

    探测对象之间的关系

    总结:

    • <type 'type'>对应的是PyType_Type对象
    • <type 'object'>对应的是PyBaseObject_Type对象
    • Python中,任何一个对象都有一个type(类型),可以通过对象的__class__属性获得;
    • 任何一个instance对象的type(类型)都是一个class对象,而任何一个class对象的type(类型)都是metaclass对象;
    • 任何一个class对象都直接或间接与<type 'object'>对象之间存在is-kind-of关系(包括<type 'type'>
    • 可能会感到疑惑的是,<type 'type'>实例化的<type 'object'>,为什么<type 'object'>还是<type 'type'>的基类?
      • <type 'type'><type 'object'>对应到C语言中,是两个同等级的对象,没有任何直接关系,是通过结构体的某个域联系在了一起。
      • 上面提到的两个关系,只是在不同纬度进行了设计,不会出现其他问题。

    0x02 从type对象到class对象

    • Python 2.2之前,为什么内置的对象不能被继承?

    比如int被继承了以后,子类想调用int.__add__方法。Python虚拟机不知道要调用的是PyInt_Type.tp_as_number.nb_addPython虚拟机不具有在type中寻找某个属性的机制

    在Python 2.2之后寻找属性的机制

    如上图所示,当Python虚拟机需要调用int.__add__时,它可以到符号int对应的class对象(PyInt_Type对象)的tp_dict指向的dict对象中查找符号__add__对应的操作,并调用该操作,从而完成对int.__add__的调用。

    额外知识点:可调用性(callable

    • Python中,只要一个对象对应的class对象中实现了__call__操作,换句话说就是在Python内部的PyTypeObject中,tp_call不为空,那么这个对象就是一个可调用对象。

    • Python中,所谓调用,就是执行对象的type所对应的class对象的tp_call操作

    tp_dict在运行时会指向一个dict对象,这个dict对象必须在运行时动态构建。

    Python 2.2开始,Python在启动时,会对类型系统(对象模型)进行从初始化,这个初始化动作会动态的在内置类型对应的PyTypeObject中填充一些重要东西,包括tp_dict。从而完成内置类型从type对象到class对象的转变。

    类型系统初始化在_Py_ReadyTypes函数中进行,其中会调用到PyType_Readyclass对象进行初始化。

    PyType_Ready仅仅是对class对象进行初始化动作的一部分(类型对象这时已经存在,只进行一些完善工作),它会处理Python内置类型和用户自定义类型。

    • 内置类型list对应的class对象PyList_TypePython启动后已经作为全局对象存在了,需要的仅仅是完善。即对于list来说,初始化就只剩下PyType_Ready了。
    • 自定义类型A对应的class对象并不存在,需要申请内存、创建并初始化整个动作序列。所以对于自定义类型A来说,PyType_Ready仅仅是很小一部分。

    处理基类和type信息

    // typeobject.c
    int
    PyType_Ready(PyTypeObject *type)
    {
        PyObject *dict, *bases;
        PyTypeObject *base;
        Py_ssize_t i, n;
    
        /* Initialize tp_base (defaults to BaseObject unless that's us) */
        // 尝试获取type的tp_base中指定的基类(super type)
        base = type->tp_base;
        if (base == NULL && type != &PyBaseObject_Type) {
            base = type->tp_base = &PyBaseObject_Type;
            Py_INCREF(base);
        }
    
        /* Initialize the base class */
        // 如果基类没有初始化,先初始化基类
        if (base && base->tp_dict == NULL) {
            if (PyType_Ready(base) < 0)
                goto error;
        }
        // 设置type信息
        if (type->ob_type == NULL && base != NULL)
            type->ob_type = base->ob_type;
        ......
    }
    
    // 调用
    PyType_Ready(&PyType_Type)
    

    进入PyType_Ready函数以后,首先会获取class对象(上例中就是PyType_Type)的基类,如果基类为NULL,会指定一个默认基类PyBaseObject_Typeobject)。承前面讲的,Python所有class对象都直接或者间接的以<type 'object'>作为基类。

    然后判断基类是否已经初始化(其中有一个条件是base->tp_dict == NULL,对应了前面所说的内容),基类没有初始化的话,先初始化基类。

    然后设置class对象的ob_type信息(就是对象__class__返回的信息)。这里设置的ob_typetype->ob_type)就是metaclass,实际上,Python虚拟机是将基类的metaclass作为子类的metaclasstype->ob_type = base->ob_type),基类PyBaseObject_TypemetaclassPyType_Type,所以到头来PyType_Typemetaclass还是自己(PyType_Type)。

    处理基类列表

    接下来处理类型的基类列表,因为Python支持多重继承,所以每一个Pythonclass对象都会有一个基类列表。

    // typeobject.c
    int PyType_Ready(PyTypeObject *type)
    {
        PyObject *dict, *bases;
        PyTypeObject *base;
        Py_ssize_t i, n;
    
        /* Initialize tp_base (defaults to BaseObject unless that's us) */
        // 尝试获取type的tp_base中指定的基类(super type)
        base = type->tp_base;
        if (base == NULL && type != &PyBaseObject_Type) {
            base = type->tp_base = &PyBaseObject_Type;
            Py_INCREF(base);
        }
        ......
        /* Initialize tp_bases */
        // 处理基类列表bases
        bases = type->tp_bases;
        if (bases == NULL) {
            // 如果基类列表bases为空,则根据base的情况设置bases
            if (base == NULL)
                bases = PyTuple_New(0);
            else
                bases = PyTuple_Pack(1, base);
            if (bases == NULL)
                goto error;
            type->tp_bases = bases;
        }
        ......
    }
    
    // 调用
    PyType_Ready(&PyBaseObject_Type)
    

    对于我们考察的PyBaseObject_Type来说,其tp_bases为空,base也为NULL,所以它的基类列表时一个空的tuple对象。

    而对于PyType_Type和其他类型(PyInt_Type等)来说,虽然tp_bases为空,但是base不为NULL(而是PyBaseObject_Type),所以它们的基类列表不为空,都包含一个PyBaseObject_Type

    填充tp_dict

    接下来进入最关键的填充tp_dict阶段,这个过程相对比较复杂。

    // typeobject.c
    int PyType_Ready(PyTypeObject *type)
    {
        PyObject *dict, *bases;
        PyTypeObject *base;
        Py_ssize_t i, n;
    
        ......
    
        /* Initialize tp_dict */
        // 设定tp_dict
        dict = type->tp_dict;
        if (dict == NULL) {
            dict = PyDict_New();
            if (dict == NULL)
                goto error;
            type->tp_dict = dict;
        }
    
        /* Add type-specific descriptors to tp_dict */
        // 将与type相关的descriptor加入到tp_dict中
        add_operators(type)
        if (type->tp_methods != NULL) {
            if (add_methods(type, type->tp_methods) < 0)
                goto error;
        }
        if (type->tp_members != NULL) {
            if (add_members(type, type->tp_members) < 0)
                goto error;
        }
        if (type->tp_getset != NULL) {
            if (add_getset(type, type->tp_getset) < 0)
                goto error;
        }
    
        ......
    }
    

    在这个阶段,完成了将("__add__", &nb_add)加入到tp_dict的过程。add_operatorsadd_methodsadd_membersadd_getset都是完成这样填充tp_dict的动作。

    Python虚拟机如何知道"__add__"nb_add之间存在关联呢?这种关联是在Python源代码中预先就确定好的,存放在一个名为slotdefs的全局数组中,如下所示:

    // typeobject.c
    static slotdef slotdefs[] = {
        TPSLOT("__str__", tp_print, NULL, NULL, ""),
        TPSLOT("__repr__", tp_print, NULL, NULL, ""),
        TPSLOT("__getattribute__", tp_getattr, NULL, NULL, ""),
        TPSLOT("__getattr__", tp_getattr, NULL, NULL, ""),
        TPSLOT("__setattr__", tp_setattr, NULL, NULL, ""),
        TPSLOT("__delattr__", tp_setattr, NULL, NULL, ""),
        TPSLOT("__cmp__", tp_compare, _PyObject_SlotCompare, wrap_cmpfunc,
               "x.__cmp__(y) <==> cmp(x,y)"),
        TPSLOT("__repr__", tp_repr, slot_tp_repr, wrap_unaryfunc,
               "x.__repr__() <==> repr(x)"),
        TPSLOT("__hash__", tp_hash, slot_tp_hash, wrap_hashfunc,
               "x.__hash__() <==> hash(x)"),
        FLSLOT("__call__", tp_call, slot_tp_call, (wrapperfunc)wrap_call,
               "x.__call__(...) <==> x(...)", PyWrapperFlag_KEYWORDS),
        TPSLOT("__str__", tp_str, slot_tp_str, wrap_unaryfunc,
               "x.__str__() <==> str(x)"),
        TPSLOT("__getattribute__", tp_getattro, slot_tp_getattr_hook,
               wrap_binaryfunc, "x.__getattribute__('name') <==> x.name"),
        TPSLOT("__getattr__", tp_getattro, slot_tp_getattr_hook, NULL, ""),
        TPSLOT("__setattr__", tp_setattro, slot_tp_setattro, wrap_setattr,
               "x.__setattr__('name', value) <==> x.name = value"),
        TPSLOT("__delattr__", tp_setattro, slot_tp_setattro, wrap_delattr,
               "x.__delattr__('name') <==> del x.name"),
        TPSLOT("__lt__", tp_richcompare, slot_tp_richcompare, richcmp_lt,
               "x.__lt__(y) <==> x<y"),
        TPSLOT("__le__", tp_richcompare, slot_tp_richcompare, richcmp_le,
               "x.__le__(y) <==> x<=y"),
        TPSLOT("__eq__", tp_richcompare, slot_tp_richcompare, richcmp_eq,
               "x.__eq__(y) <==> x==y"),
        TPSLOT("__ne__", tp_richcompare, slot_tp_richcompare, richcmp_ne,
               "x.__ne__(y) <==> x!=y"),
        TPSLOT("__gt__", tp_richcompare, slot_tp_richcompare, richcmp_gt,
               "x.__gt__(y) <==> x>y"),
        TPSLOT("__ge__", tp_richcompare, slot_tp_richcompare, richcmp_ge,
               "x.__ge__(y) <==> x>=y"),
        TPSLOT("__iter__", tp_iter, slot_tp_iter, wrap_unaryfunc,
               "x.__iter__() <==> iter(x)"),
        TPSLOT("next", tp_iternext, slot_tp_iternext, wrap_next,
               "x.next() -> the next value, or raise StopIteration"),
        TPSLOT("__get__", tp_descr_get, slot_tp_descr_get, wrap_descr_get,
               "descr.__get__(obj[, type]) -> value"),
        TPSLOT("__set__", tp_descr_set, slot_tp_descr_set, wrap_descr_set,
               "descr.__set__(obj, value)"),
        TPSLOT("__delete__", tp_descr_set, slot_tp_descr_set,
               wrap_descr_delete, "descr.__delete__(obj)"),
        FLSLOT("__init__", tp_init, slot_tp_init, (wrapperfunc)wrap_init,
               "x.__init__(...) initializes x; "
               "see help(type(x)) for signature",
               PyWrapperFlag_KEYWORDS),
        TPSLOT("__new__", tp_new, slot_tp_new, NULL, ""),
        TPSLOT("__del__", tp_del, slot_tp_del, NULL, ""),
        BINSLOT("__add__", nb_add, slot_nb_add,
            "+"),
        RBINSLOT("__radd__", nb_add, slot_nb_add,
                 "+"),
        BINSLOT("__sub__", nb_subtract, slot_nb_subtract,
            "-"),
        RBINSLOT("__rsub__", nb_subtract, slot_nb_subtract,
                 "-"),
        BINSLOT("__mul__", nb_multiply, slot_nb_multiply,
            "*"),
        RBINSLOT("__rmul__", nb_multiply, slot_nb_multiply,
                 "*"),
        BINSLOT("__div__", nb_divide, slot_nb_divide,
            "/"),
        RBINSLOT("__rdiv__", nb_divide, slot_nb_divide,
                 "/"),
        BINSLOT("__mod__", nb_remainder, slot_nb_remainder,
            "%"),
        RBINSLOT("__rmod__", nb_remainder, slot_nb_remainder,
                 "%"),
        BINSLOTNOTINFIX("__divmod__", nb_divmod, slot_nb_divmod,
            "divmod(x, y)"),
        RBINSLOTNOTINFIX("__rdivmod__", nb_divmod, slot_nb_divmod,
                 "divmod(y, x)"),
        NBSLOT("__pow__", nb_power, slot_nb_power, wrap_ternaryfunc,
               "x.__pow__(y[, z]) <==> pow(x, y[, z])"),
        NBSLOT("__rpow__", nb_power, slot_nb_power, wrap_ternaryfunc_r,
               "y.__rpow__(x[, z]) <==> pow(x, y[, z])"),
        UNSLOT("__neg__", nb_negative, slot_nb_negative, wrap_unaryfunc, "-x"),
        UNSLOT("__pos__", nb_positive, slot_nb_positive, wrap_unaryfunc, "+x"),
        UNSLOT("__abs__", nb_absolute, slot_nb_absolute, wrap_unaryfunc,
               "abs(x)"),
        UNSLOT("__nonzero__", nb_nonzero, slot_nb_nonzero, wrap_inquirypred,
               "x != 0"),
        UNSLOT("__invert__", nb_invert, slot_nb_invert, wrap_unaryfunc, "~x"),
        BINSLOT("__lshift__", nb_lshift, slot_nb_lshift, "<<"),
        RBINSLOT("__rlshift__", nb_lshift, slot_nb_lshift, "<<"),
        BINSLOT("__rshift__", nb_rshift, slot_nb_rshift, ">>"),
        RBINSLOT("__rrshift__", nb_rshift, slot_nb_rshift, ">>"),
        BINSLOT("__and__", nb_and, slot_nb_and, "&"),
        RBINSLOT("__rand__", nb_and, slot_nb_and, "&"),
        BINSLOT("__xor__", nb_xor, slot_nb_xor, "^"),
        RBINSLOT("__rxor__", nb_xor, slot_nb_xor, "^"),
        BINSLOT("__or__", nb_or, slot_nb_or, "|"),
        RBINSLOT("__ror__", nb_or, slot_nb_or, "|"),
        NBSLOT("__coerce__", nb_coerce, slot_nb_coerce, wrap_coercefunc,
               "x.__coerce__(y) <==> coerce(x, y)"),
        UNSLOT("__int__", nb_int, slot_nb_int, wrap_unaryfunc,
               "int(x)"),
        UNSLOT("__long__", nb_long, slot_nb_long, wrap_unaryfunc,
               "long(x)"),
        UNSLOT("__float__", nb_float, slot_nb_float, wrap_unaryfunc,
               "float(x)"),
        UNSLOT("__oct__", nb_oct, slot_nb_oct, wrap_unaryfunc,
               "oct(x)"),
        UNSLOT("__hex__", nb_hex, slot_nb_hex, wrap_unaryfunc,
               "hex(x)"),
        IBSLOT("__iadd__", nb_inplace_add, slot_nb_inplace_add,
               wrap_binaryfunc, "+="),
        IBSLOT("__isub__", nb_inplace_subtract, slot_nb_inplace_subtract,
               wrap_binaryfunc, "-="),
        IBSLOT("__imul__", nb_inplace_multiply, slot_nb_inplace_multiply,
               wrap_binaryfunc, "*="),
        IBSLOT("__idiv__", nb_inplace_divide, slot_nb_inplace_divide,
               wrap_binaryfunc, "/="),
        IBSLOT("__imod__", nb_inplace_remainder, slot_nb_inplace_remainder,
               wrap_binaryfunc, "%="),
        IBSLOT("__ipow__", nb_inplace_power, slot_nb_inplace_power,
               wrap_binaryfunc, "**="),
        IBSLOT("__ilshift__", nb_inplace_lshift, slot_nb_inplace_lshift,
               wrap_binaryfunc, "<<="),
        IBSLOT("__irshift__", nb_inplace_rshift, slot_nb_inplace_rshift,
               wrap_binaryfunc, ">>="),
        IBSLOT("__iand__", nb_inplace_and, slot_nb_inplace_and,
               wrap_binaryfunc, "&="),
        IBSLOT("__ixor__", nb_inplace_xor, slot_nb_inplace_xor,
               wrap_binaryfunc, "^="),
        IBSLOT("__ior__", nb_inplace_or, slot_nb_inplace_or,
               wrap_binaryfunc, "|="),
        BINSLOT("__floordiv__", nb_floor_divide, slot_nb_floor_divide, "//"),
        RBINSLOT("__rfloordiv__", nb_floor_divide, slot_nb_floor_divide, "//"),
        BINSLOT("__truediv__", nb_true_divide, slot_nb_true_divide, "/"),
        RBINSLOT("__rtruediv__", nb_true_divide, slot_nb_true_divide, "/"),
        IBSLOT("__ifloordiv__", nb_inplace_floor_divide,
               slot_nb_inplace_floor_divide, wrap_binaryfunc, "//="),
        IBSLOT("__itruediv__", nb_inplace_true_divide,
               slot_nb_inplace_true_divide, wrap_binaryfunc, "/="),
        NBSLOT("__index__", nb_index, slot_nb_index, wrap_unaryfunc,
               "x[y:z] <==> x[y.__index__():z.__index__()]"),
        MPSLOT("__len__", mp_length, slot_mp_length, wrap_lenfunc,
               "x.__len__() <==> len(x)"),
        MPSLOT("__getitem__", mp_subscript, slot_mp_subscript,
               wrap_binaryfunc,
               "x.__getitem__(y) <==> x[y]"),
        MPSLOT("__setitem__", mp_ass_subscript, slot_mp_ass_subscript,
               wrap_objobjargproc,
               "x.__setitem__(i, y) <==> x[i]=y"),
        MPSLOT("__delitem__", mp_ass_subscript, slot_mp_ass_subscript,
               wrap_delitem,
               "x.__delitem__(y) <==> del x[y]"),
        SQSLOT("__len__", sq_length, slot_sq_length, wrap_lenfunc,
               "x.__len__() <==> len(x)"),
        /* Heap types defining __add__/__mul__ have sq_concat/sq_repeat == NULL.
           The logic in abstract.c always falls back to nb_add/nb_multiply in
           this case.  Defining both the nb_* and the sq_* slots to call the
           user-defined methods has unexpected side-effects, as shown by
           test_descr.notimplemented() */
        SQSLOT("__add__", sq_concat, NULL, wrap_binaryfunc,
          "x.__add__(y) <==> x+y"),
        SQSLOT("__mul__", sq_repeat, NULL, wrap_indexargfunc,
          "x.__mul__(n) <==> x*n"),
        SQSLOT("__rmul__", sq_repeat, NULL, wrap_indexargfunc,
          "x.__rmul__(n) <==> n*x"),
        SQSLOT("__getitem__", sq_item, slot_sq_item, wrap_sq_item,
               "x.__getitem__(y) <==> x[y]"),
        SQSLOT("__getslice__", sq_slice, slot_sq_slice, wrap_ssizessizeargfunc,
               "x.__getslice__(i, j) <==> x[i:j]\n\
               \n\
               Use of negative indices is not supported."),
        SQSLOT("__setitem__", sq_ass_item, slot_sq_ass_item, wrap_sq_setitem,
               "x.__setitem__(i, y) <==> x[i]=y"),
        SQSLOT("__delitem__", sq_ass_item, slot_sq_ass_item, wrap_sq_delitem,
               "x.__delitem__(y) <==> del x[y]"),
        SQSLOT("__setslice__", sq_ass_slice, slot_sq_ass_slice,
               wrap_ssizessizeobjargproc,
               "x.__setslice__(i, j, y) <==> x[i:j]=y\n\
               \n\
               Use  of negative indices is not supported."),
        SQSLOT("__delslice__", sq_ass_slice, slot_sq_ass_slice, wrap_delslice,
               "x.__delslice__(i, j) <==> del x[i:j]\n\
               \n\
               Use of negative indices is not supported."),
        SQSLOT("__contains__", sq_contains, slot_sq_contains, wrap_objobjproc,
               "x.__contains__(y) <==> y in x"),
        SQSLOT("__iadd__", sq_inplace_concat, NULL,
          wrap_binaryfunc, "x.__iadd__(y) <==> x+=y"),
        SQSLOT("__imul__", sq_inplace_repeat, NULL,
          wrap_indexargfunc, "x.__imul__(y) <==> x*=y"),
        {NULL}
    };
    

    slot与操作排序

    Python内部,slot表示PyTypeObject中定义的操作,一个操作对应一个slot

    // typeobject.c
    typedef struct wrapperbase slotdef;
    
    // descrobject.h
    struct wrapperbase {
        char *name;
        int offset;
        void *function;
        wrapperfunc wrapper;
        char *doc;
        int flags;
        PyObject *name_strobj;
    };
    
    • 一个slot对象不仅包含一个函数指针,还有其他一些信息。
    • name域存储操作对应的名称,即__add__
    • offset域存储操作的函数地址在PyHeapTypeObject中的偏移量
    • function域指向一种称为slot function的函数
    // typeobject.c
    #define TPSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
        {NAME, offsetof(PyTypeObject, SLOT), (void *)(FUNCTION), WRAPPER, \
         PyDoc_STR(DOC)}
    
    #define FLSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC, FLAGS) \
        {NAME, offsetof(PyTypeObject, SLOT), (void *)(FUNCTION), WRAPPER, \
         PyDoc_STR(DOC), FLAGS}
    
    #define ETSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
        {NAME, offsetof(PyHeapTypeObject, SLOT), (void *)(FUNCTION), WRAPPER, \
         PyDoc_STR(DOC)}
    
    #define SQSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
        ETSLOT(NAME, as_sequence.SLOT, FUNCTION, WRAPPER, DOC)
    
    #define MPSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
        ETSLOT(NAME, as_mapping.SLOT, FUNCTION, WRAPPER, DOC)
    
    #define NBSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
        ETSLOT(NAME, as_number.SLOT, FUNCTION, WRAPPER, DOC)
    
    #define UNSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
        ETSLOT(NAME, as_number.SLOT, FUNCTION, WRAPPER, \
               "x." NAME "() <==> " DOC)
    
    #define IBSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
        ETSLOT(NAME, as_number.SLOT, FUNCTION, WRAPPER, \
               "x." NAME "(y) <==> x" DOC "y")
    
    #define BINSLOT(NAME, SLOT, FUNCTION, DOC) \
        ETSLOT(NAME, as_number.SLOT, FUNCTION, wrap_binaryfunc_l, \
               "x." NAME "(y) <==> x" DOC "y")
    
    #define RBINSLOT(NAME, SLOT, FUNCTION, DOC) \
        ETSLOT(NAME, as_number.SLOT, FUNCTION, wrap_binaryfunc_r, \
               "x." NAME "(y) <==> y" DOC "x")
    
    #define BINSLOTNOTINFIX(NAME, SLOT, FUNCTION, DOC) \
        ETSLOT(NAME, as_number.SLOT, FUNCTION, wrap_binaryfunc_l, \
               "x." NAME "(y) <==> " DOC)
    
    #define RBINSLOTNOTINFIX(NAME, SLOT, FUNCTION, DOC) \
        ETSLOT(NAME, as_number.SLOT, FUNCTION, wrap_binaryfunc_r, \
               "x." NAME "(y) <==> " DOC)
    

    上面是创建一个slot使用到的宏,最基本的就是TPSLOTETSLOT(其他宏都是对这两个宏的简单包装)。

    TPSLOTETSLOT的区别在于TPSLOT计算的是操作对应的函数指针(比如nb_add等)在PyTypeObject中的偏移量;而ETSLOT计算的是函数指针在PyHeapTypeObject中的偏移量。

    // object.h
    typedef struct _heaptypeobject {
        PyTypeObject ht_type;
        PyNumberMethods as_number;
        PyMappingMethods as_mapping;
        PySequenceMethods as_sequence; 
        PyBufferProcs as_buffer;
        PyObject *ht_name, *ht_slots;
        /* here are optional user slots, followed by the members. */
    } PyHeapTypeObject;
    

    上面代码给出了PyHeapTypeObject的结构,第一个域就是PyTypeObject对象,所以TPSLOT计算出的偏移量实际上也就是相当于PyHeapTypeObject的偏移量。

    对于一个PyTypeObject对象来说,有的操作,比如nb_add,其函数指针是在PyNumberMethods中存放的,但是PyTypeObject中却是通过一个tp_as_number指针指向一个PyNumberMethods结构。所以,根本没法计算出nb_addPyTypeObject中的偏移量,只能计算其在PyHeapTypeObject中的偏移量(因为PyHeapTypeObject中存储的是PyNumberMethods对象,而不只是一个指针)。

    So,与nb_add对应的slot必须是由ETSLOT定义的。但是问题来了,nb_add对应的slot中记录的offset是基于PyHeapTypeObject的,而PyInt_Type却是一个PyTypeObject,那么显然根据这个偏移量不可能拿到PyInt_Type中为int准备的nb_add

    其实这个offset是用来对操作进行排序的。下面对这个排序进行详解。

    // typeobject.c
    static slotdef slotdefs[] = {
        ......
        // 不同操作名对应相同操作
        BINSLOT("__add__", nb_add, slot_nb_add,
            "+"),
        RBINSLOT("__radd__", nb_add, slot_nb_add,
                 "+"),
        // 相同操作名对应不同操作
        SQSLOT("__getitem__", sq_item, slot_sq_item, wrap_sq_item,
               "x.__getitem__(y) <==> x[y]"),
        MPSLOT("__getitem__", mp_subscript, slot_mp_subscript,
               wrap_binaryfunc,
               "x.__getitem__(y) <==> x[y]")
        ......
    };
    

    如上面代码,Python定义的slotdefs数组中,会有不同操作名对应相同操作相同操作名对应不同操作的情况。

    对于相同操作名对应不同操作的情况,在填充tp_dict时会出现问题:比如对于操作名__getitem__,在tp_dict中与其对应的是sq_item还是mp_subscript

    slot中的offset信息就是对slot(也就是操作)进行排序的。

    对照之前PyHeapTypeObject结构,结构体中各个域的顺序很重要(顺序隐含了操作优先级的信息),比如在PyHeapTypeObject中,PyMappingMethods的实例mp_subscript的位置在PySequenceMethods的实例sq_item之前,所以最终计算出的偏移存在如下关系:offset(mp_subscript)<offset(sq_item)。如果一个PyTypeObject,既定义了mp_subscript又定义了sq_item,那么Python虚拟机将选择mp_subscript__getitem__建立联系。

    整个对slotdefs的排序在中进行:

    // typeobject.c
    static void init_slotdefs(void)
    {
        slotdef *p;
        static int initialized = 0;
        // 只执行一次init_slotdefs
        if (initialized)
            return;
        for (p = slotdefs; p->name; p++) {
            /* Slots must be ordered by their offset in the PyHeapTypeObject. */
            // 根据偏移量进行排序
            assert(!p[1].name || p->offset <= p[1].offset);
            // 填充slotdef结构体中的name_strobj
            p->name_strobj = PyString_InternFromString(p->name);
            if (!p->name_strobj || !PyString_CHECK_INTERNED(p->name_strobj))
                Py_FatalError("Out of memory interning slotdef names");
        }
        initialized = 1;
    }
    

    从slot到descriptor

    前面说到,Python虚拟机在tp_dict中找到__getitem__对应的“操作”后,会调用该“操作”。所以与__getitem__关联在一起的肯定不是slot(因为slot不是一个PyObject,不能存放在字典中,不是PyObject的话也就没有type,也没有tp_call了,所以也不能调用),而是包装了slotPyObject对象,称为descriptor

    Python中存在多种descriptor,与PyTypeObject中的操作对应的是PyWrapperDescrObject

    // descrobject.h
    #define PyDescr_COMMON \
        PyObject_HEAD \
        PyTypeObject *d_type; \
        PyObject *d_name
    
    typedef struct {
        PyDescr_COMMON;
        struct wrapperbase *d_base;
        void *d_wrapped; /* This can be any function pointer */
    } PyWrapperDescrObject;
    
    // descrobject.c
    PyObject *
    PyDescr_NewWrapper(PyTypeObject *type, struct wrapperbase *base, void *wrapped)
    {
        PyWrapperDescrObject *descr;
    
        descr = (PyWrapperDescrObject *)descr_new(&PyWrapperDescr_Type,
                             type, base->name);
        if (descr != NULL) {
            descr->d_base = base;
            descr->d_wrapped = wrapped;
        }
        return (PyObject *)descr;
    }
    
    static PyDescrObject *
    descr_new(PyTypeObject *descrtype, PyTypeObject *type, const char *name)
    {
        PyDescrObject *descr;
            // 申请空间
        descr = (PyDescrObject *)PyType_GenericAlloc(descrtype, 0);
        if (descr != NULL) {
            Py_XINCREF(type);
            descr->d_type = type;
            descr->d_name = PyString_InternFromString(name);
            if (descr->d_name == NULL) {
                Py_DECREF(descr);
                descr = NULL;
            }
        }
        return descr;
    }
    

    Python内的各种descriptor都包含PyDescr_COMMON头。

    PyWrapperDescrObject中的d_type被设置为PyDescr_NewWrapper的参数typePyWrapperDescr_Type);d_wrapped存放着重要的信息,即操作对应的函数指针(比如对于PyList_Type来说,其tp_dict["__getitem__"].d_wrapped就是&mp_subscript);d_base存储着slot

    PyWrapperDescr_Typetp_callwrapperdescr_call,当Python虚拟机“调用”一个descriptor时,也就会调用wrapperdescr_call函数。

    建立联系

    上面排序后的slotdefs仍然存放在slotdefs中,然后Python虚拟机遍历slotdefs,基于每一个slot创建一个descriptor,最后在tp_dict中建立从操作名到descriptor的关联。这个操作在下面代码中实现:

    // typeobject.c
    static int add_operators(PyTypeObject *type)
    {
        PyObject *dict = type->tp_dict;
        slotdef *p;
        PyObject *descr;
        void **ptr;
        // 对slotdefs进行排序
        init_slotdefs();
        for (p = slotdefs; p->name; p++) {
            // 如果slot中没有指定wrapper,则不处理
            if (p->wrapper == NULL)
                continue;
            // 获得slot对应的操作在PyTypeObject中的函数指针
            ptr = slotptr(type, p->offset);
            if (!ptr || !*ptr)
                continue;
            // 如果tp_dict中已经存在操作名,则放弃
            if (PyDict_GetItem(dict, p->name_strobj))
                continue;
            // 创建descriptor
            descr = PyDescr_NewWrapper(type, p, *ptr);
            if (descr == NULL)
                return -1;
            // 将(操作名,descriptor)放入tp_dict中
            if (PyDict_SetItem(dict, p->name_strobj, descr) < 0)
                return -1;
            Py_DECREF(descr);
        }
        if (type->tp_new != NULL) {
            if (add_tp_new_wrapper(type) < 0)
                return -1;
        }
        return 0;
    }
    

    上面的过程很简单,先对slotdefs进行排序,然后遍历slotdefs,通过slotptr()函数获取到slot对应操作在PyTypeObject中的函数指针,如果tp_dict中已经存在相同的操作名则忽略(正因为这个机制,使得Python虚拟机能够在拥有相同操作名的多个操作中选择优先级最高的操作),然后创建descriptor,最后在tp_dict中建立操作名(slotdef->name_strobj)到操作(descriptor)的关联。

    slotptr()获取slot对应操作在PyTypeObject中的函数指针时,进行了比较复杂的过程:

    比如说调用slotptr(&PyList_Type, offset(PyHeapTypeObject, mp_subscript))

    如下图说示,offset(PyHeapTypeObject, mp_subscript)的偏移肯定大于offset(PyHeapTypeObject, as_mapping),所以会先从PyTypeObject对象中获得as_mapping的指针p,然后在p的基础上进行偏移就可以得到实际的函数地址,偏移量计算方式:delta = offset(PyHeapTypeObject, mp_subscript) - offset(PyHeapTypeObject, as_mapping)

    PyHeapTypeObject结构展开式
    slotptr具体判断是从PyHeapTypeObject中排在最后的PySequenceMethods开始(肯定是从后往前,来确定属于哪个*Methods),具体代码如下:
    static void **
    slotptr(PyTypeObject *type, int ioffset)
    {
        char *ptr;
        long offset = ioffset;
    
        /* Note: this depends on the order of the members of PyHeapTypeObject! */
        // 判断从PyHeapTypeObject中排在最后的PySequenceMethods开始
        assert(offset >= 0);
        assert((size_t)offset < offsetof(PyHeapTypeObject, as_buffer));
        if ((size_t)offset >= offsetof(PyHeapTypeObject, as_sequence)) {
            ptr = (char *)type->tp_as_sequence;
            offset -= offsetof(PyHeapTypeObject, as_sequence);
        }
        else if ((size_t)offset >= offsetof(PyHeapTypeObject, as_mapping)) {
            ptr = (char *)type->tp_as_mapping;
            offset -= offsetof(PyHeapTypeObject, as_mapping);
        }
        else if ((size_t)offset >= offsetof(PyHeapTypeObject, as_number)) {
            ptr = (char *)type->tp_as_number;
            offset -= offsetof(PyHeapTypeObject, as_number);
        }
        else {
            ptr = (char *)type;
        }
        if (ptr != NULL)
            ptr += offset;
        return (void **)ptr;
    }
    

    下面展示了PyList_Type完成初始化之后的整个布局。

    add_operator完成后的PyList_Type

    再回到PyType_Ready中,在通过add_operators添加了PyTypeObject对象中定义的一些operator后,还会通过add_methodsadd_membersadd_getsets添加在PyTypeObject中定义的tp_methodstp_memberstp_getset函数集。这些add_***的过程与add_operators类似,不过最后添加到tp_dict中的descriptor不再是PyWrapperDescrObject,而是PyMethodDescrObjectPyMemberDescrObjectPyGetSetDescrObject

    确定MRO

    所谓MRO,就是Method Resolve Order(对象的属性解析顺序)

    Python内部在PyType_Ready中通过mro_internal函数完成对一个类型的mro顺序的建立Python虚拟机将创建一个tuple对象,在对象中一次存放着一组class对象,在tupleclass对象的顺序就是Python虚拟机在解析属性时的mro顺序。最终这个tuple将被保存在PyTypeObject.tp_mro中。

    # example.py
    class A(list):
        def show(self):
            print "A::show"
    
    class B(list):
        def show(self):
            print "B::show"
    
    class C(A):
        pass
    
    class D(C, B):
        pass
    
    d = D()
    d.show()
    

    参考上面的例子,Python虚拟机会在内部创建一个list,其中根据D的声明顺序,依次放入D和它的基类,列表最后一项存放着一个包含所有D的直接基类的列表。如下图所示:

    D建立MRO时内部的辅助list

    随后Python虚拟机从左到右遍历该list,当访问到list中的任一个街垒时,如果基类存在mro列表,则转而访问基类的mro列表。在访问过程中,不断将所访问到的class对象放入到D自身的mro类表中。

    详细步骤:

    • 首先获得DDmro列表(tp_mro)中没有D,所以放入D
    • 获得CDmro列表中没有C,所以放入C。发现C中存在mro列表,转而访问Cmro列表:
      • 获得ADmro列表中没有A,放入A
      • 获得list,这里需要注意一下,尽管Dmro列表中没有list,但是在后面的Bmro列表中出现了list,那么Python虚拟机会跳过这里的list,将list的处理推迟到处理Bmro列表时
      • 获得object,同上
    • 获得BDmro列表中没有B,所以放入B。转而访问Bmro列表:
      • 获得list,这时可以将list放入Dmro列表
      • 获得object,这时可以将object放入Dmro列表

    最后遍历得到(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'list'>, <type 'object'>)

    继承基类操作

    Python虚拟机确定了mro列表之后,就会遍历mro列表(从第二项开始遍历,第一项是自身)。

    mro列表中存储的就是class对象的所有直接和间接基类,Python虚拟机会将class对象自身没有设置而基类中设置了的操作拷贝到class对象中,从而完成对基类操作的继承动作。继承操作发生在inherit_slots中:

    // typeobject.c
    int PyType_Ready(PyTypeObject *type)
    {
        ......
        /* Initialize tp_dict properly */
        bases = type->tp_mro;
        assert(bases != NULL);
        assert(PyTuple_Check(bases));
        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);
        }
        ......
    }
    
    static void
    inherit_slots(PyTypeObject *type, PyTypeObject *base)
    {
        PyTypeObject *basebase;
    
    #define SLOTDEFINED(SLOT) \
        (base->SLOT != 0 && \
         (basebase == NULL || base->SLOT != basebase->SLOT))
    
    #define COPYSLOT(SLOT) \
        if (!type->SLOT && SLOTDEFINED(SLOT)) type->SLOT = base->SLOT
    
    #define COPYNUM(SLOT) COPYSLOT(tp_as_number->SLOT)
    #define COPYSEQ(SLOT) COPYSLOT(tp_as_sequence->SLOT)
    #define COPYMAP(SLOT) COPYSLOT(tp_as_mapping->SLOT)
    #define COPYBUF(SLOT) COPYSLOT(tp_as_buffer->SLOT)
    
        /* This won't inherit indirect slots (from tp_as_number etc.)
           if type doesn't provide the space. */
    
        if (type->tp_as_number != NULL && base->tp_as_number != NULL) {
            basebase = base->tp_base;
            if (basebase->tp_as_number == NULL)
                basebase = NULL;
            COPYNUM(nb_add);
            ......
        }
        ......
    }
    

    填充基类中的子类列表

    到这里,PyType_Ready还剩下最后一个重要的动作:设置基类中的子类列表。

    在每一个PyTypeObject中,有一个tp_subclasses,这个东西在PyType_Ready完成后将是一个list对象。其中存放着所有直接继承自该类型的class对象。

    // typeobject.c
    int PyType_Ready(PyTypeObject *type)
    {
        PyObject *dict, *bases;
        PyTypeObject *base;
        Py_ssize_t i, n;
        ......
        /* Link into each base class's list of subclasses */
        // 填充基类的子类列表
        bases = type->tp_bases;
        n = PyTuple_GET_SIZE(bases);
        for (i = 0; i < n; i++) {
            PyObject *b = PyTuple_GET_ITEM(bases, i);
            if (PyType_Check(b) &&
                add_subclass((PyTypeObject *)b, type) < 0)
                goto error;
        }
        ......
    }
    

    总结

    至此,PyType_Ready的所有动作都完成了。

    • 设置type信息、基类及基类列表
    • 填充tp_dict
    • 确定mro列表
    • 基于mro列表从基类继承操作
    • 设置基类的子类列表

    0x03 用户自定义class

    # 例子:class_0.py
    class A(object):
        name = 'python'
        def __init__(self):
            print "A::__init__"
    
        def f(self):
            print "A::f"
    
        def g(self, aValue):
            self.value = aValue
            print self.value
    
    a = A()
    a.f()
    a.g(10)
    
    class_0.py编译后的PyCodeObject对象结构

    根据上面的例子可看出,定义class和定义函数类似,声明语句所在的PyCodeObject对象和具体实现所在的PyCodeObject对象是不一样的。

    Python虚拟机开始执行class_0.py时,第一步就是执行class A(object):,创建class对象。

    创建class对象

    class的动态元信息

    所谓class的元信息,就是class的名称、它所拥有的属性和方法、该class实例化时要为实例对象申请的内存空间的大小等。

    对于上面的例子class_0.py来说,我们必须知道这些信息:在class A中,有一个符号f,对应一个函数对象;还有一个符号g,也对应一个函数对象。

    编译后的符号表和常量表

    接下来来看字节码指令:

    # 分析 class A(object): 的字节码
    1             0 LOAD_CONST               0 ('A')
                  3 LOAD_NAME                0 (object)
                  6 BUILD_TUPLE              1
                  9 LOAD_CONST               1 (<code object A at 0000000004821AB0, file "test3.py", line 1>)
                 12 MAKE_FUNCTION            0
                 15 CALL_FUNCTION            0
                 18 BUILD_CLASS
                 19 STORE_NAME               1 (A)
    

    熟悉的指令就先不看了。

    3 LOAD_NAME 0 (object)6 BUILD_TUPLE 1指令时非常关键的,这两条指令根据类A的所有基类,创建一个基类列表。

    然后9 LOAD_CONST 1指令将类A对应的PyCodeObject对象入栈,接下来MAKE_FUNCTION创建一个PyFunctionObject对象。

    此时的运行时栈
    随后Python虚拟机开始执行CALL_FUNCTION指令,之前我们讲CALL_FUNCTION的最后会创建一个新的PyFrameObject对象,并开始执行这个对象中所包含的字节码序列。对应到目前的情况,执行的字节码指令是类A对应的字节码指令。
    1           0 LOAD_NAME                0 (__name__)
                3 STORE_NAME               1 (__module__)
    # name = 'python'
    2           6 LOAD_CONST               0 ('python')
                9 STORE_NAME               2 (name)
    # def __init__(self):
    3          12 LOAD_CONST               1 (<code object __init__ at 0000000004821B30, file "test3.py", line 3>)
               15 MAKE_FUNCTION            0
               18 STORE_NAME               3 (__init__)
    # def f(self):
    6          21 LOAD_CONST               2 (<code object f at 00000000048219B0, file "test3.py", line 6>)
               24 MAKE_FUNCTION            0
               27 STORE_NAME               4 (f)
    # def g(self, aValue):
    9          30 LOAD_CONST               3 (<code object g at 0000000004821BB0, file "test3.py", line 9>)
               33 MAKE_FUNCTION            0
               36 STORE_NAME               5 (g)
    
               39 LOAD_LOCALS
               40 RETURN_VALUE
    

    开始的LOAD_NAMESTORE_NAME将符号__module__和全局名字空间中的符号__name__对应的值(__main__)关联起来,并放到local名字空间(PyFrameObject对象的f_locals)中。f_locals需要特别说明一下:函数机制中,f_locals是被置为NULL的,局部变量是以一种位置参数的形式存放在f_localsplus中(运行时栈之前的一段内存区域);而此时的f_locals是被创建了,然后指向了一个PyDictObject对象。

    接着连续执行三个(LOAD_CONSTMAKE_FUNCTIONSTORE_NAME)指令序列,每个指令序列都会创建一个与类中成员函数对应的PyFunctionObject对象,并与函数名通过STORE_NAME指令存储到local名字空间中。

    此时,local名字空间中存放的东西就是class A的动态元信息。

    通过LOAD_LOCALS指令将local名字空间(f_locals)中的元信息入栈,然后返回给上一个栈帧。

    然后CALL_FUNCTION指令的最后,将返回来的值压入当前栈帧的运行时栈中;

    CALL_FUNCTION指令完成后的运行时栈
    这时就转到了class_0.py所对应的字节码序列中的BUILD_CLASS字节码指令。

    metaclass

    // ceval.c
    PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
    {
      ......
      case BUILD_CLASS:
          u = TOP();        // class的动态元信息f_locals
          v = SECOND();        // class的基类列表
          w = THIRD();        // class的名“A”
          STACKADJ(-2);
          x = build_class(u, v, w);
          SET_TOP(x);
          Py_DECREF(u);
          Py_DECREF(v);
          Py_DECREF(w);
          break;
      ......
    }
    

    接上面讲的,开始执行BUILD_CLASS字节码指令;

    在获得了class动态元信息(class的属性表)、class名称和class基类列表后,Python虚拟机会调用build_class创建class对象,然后将创建的class对象压入到运行时栈中。

    注意:在执行了STACKADJ(-2)后,栈顶指针向上移动了2个位置(也就是最初存储class名称"A"的位置)。

    image.png
    BUILD_CLASS指令结束后,通过STORE_NAME("A", <class A>)存放到local名字空间中,至此符号A与其对应的class对象的关系就建立起来了。
    获得metaclass

    创建class的所有动作就在BUILD_CLASS指令的build_class()函数中完成的。

    build_class()中包含了元类创建类对象的一些逻辑

    // ceval.c
    static PyObject *
    build_class(PyObject *methods, PyObject *bases, PyObject *name)
    {
        PyObject *metaclass = NULL, *result, *base;
    
        // 检查属性表中是否有指定的__metaclass__
        if (PyDict_Check(methods))
            metaclass = PyDict_GetItemString(methods, "__metaclass__");
        if (metaclass != NULL)
            Py_INCREF(metaclass);
        else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
            // 获得A的第一个基类
            base = PyTuple_GET_ITEM(bases, 0);
            // 获得object.__class__
            metaclass = PyObject_GetAttrString(base, "__class__");
            if (metaclass == NULL) {
                PyErr_Clear();
                metaclass = (PyObject *)base->ob_type;
                Py_INCREF(metaclass);
            }
        }
        else {
            PyObject *g = PyEval_GetGlobals();
            if (g != NULL && PyDict_Check(g))
                metaclass = PyDict_GetItemString(g, "__metaclass__");
            if (metaclass == NULL)
                metaclass = (PyObject *) &PyClass_Type;
            Py_INCREF(metaclass);
        }
        result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL);
        Py_DECREF(metaclass);
        if (result == NULL && PyErr_ExceptionMatches(PyExc_TypeError)) {
            /* A type error here likely means that the user passed
               in a base that was not a class (such the random module
               instead of the random.random type).  Help them out with
               by augmenting the error message with more information.*/
    
            PyObject *ptype, *pvalue, *ptraceback;
    
            PyErr_Fetch(&ptype, &pvalue, &ptraceback);
            if (PyString_Check(pvalue)) {
                PyObject *newmsg;
                newmsg = PyString_FromFormat(
                    "Error when calling the metaclass bases\n    %s",
                    PyString_AS_STRING(pvalue));
                if (newmsg != NULL) {
                    Py_DECREF(pvalue);
                    pvalue = newmsg;
                }
            }
            PyErr_Restore(ptype, pvalue, ptraceback);
        }
        return result;
    }
    

    build_class中,metaclass正是关于class对象的另一部分元信息,我们称之为静态元信息

    在静态元信息中,隐藏着所有的class对象应该如何创建的信息。

    • 首先查找class属性,看用户是否自定义了__metaclass__,如果指定了,当然是用用户自己指定的__metaclass__
    • 如果用户没有指定,则选择class的第一个基类的type作为该classmetaclass;对于我们的例子中,metaclass就是<type 'type'>对象
    • 如果没有基类的话,就在模块层面(global名字空间)中找__metaclass__属性,将它作为metaclass(静态元信息)
    • 如果global名字空间中也没有指定__metaclass__属性,就是用默认的metaclass,即<type 'type'>对象(PyClass_Type

    对于内置类型(PyIntObject等),其所有的元信息都包含在对应的类型对象中。为什么一个class对象的所有元信息不能包含在metaclass中,而要分成两部分?

    • Python源代码中会定义不同的class对象,这些class对象的属性往往都是不相同的,所以只能使用动态的机制来保存class的属性,称为动态元信息;
    • 但是不同的class对象也会有相同的部分,将相同的部分(class对象的type和创建策略等)抽取出来存放在class对象的metaclass中,称为静态元信息;
      class对象与元信息的关系图
    调用metaclass

    build_class函数的前半部分获取到metaclass以后,接下来就是调用metaclass。首先调用PyObject_CallFunctionObjArgs函数,然后调用到PyObject_Call函数中。

    注意:接上面的例子,metaclass<type 'type'>,也就是PyType_Type对象,根据传入的3个参数(类名、基类列表、类属性)的不同,调用PyType_Type对象后,会返回不同的class对象。

    // object.h
    typedef PyObject * (*ternaryfunc)(PyObject *, PyObject *, PyObject *);
    
    // abstract.c
    PyObject *
    PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
    {
        ternaryfunc call;
    
        if ((call = func->ob_type->tp_call) != NULL) {
            PyObject *result;
            if (Py_EnterRecursiveCall(" while calling a Python object"))
                return NULL;
            result = (*call)(func, arg, kw);
            Py_LeaveRecursiveCall();
            if (result == NULL && !PyErr_Occurred())
                PyErr_SetString(
                    PyExc_SystemError,
                    "NULL result without error in PyObject_Call");
            return result;
        }
        PyErr_Format(PyExc_TypeError, "'%.200s' object is not callable",
                     func->ob_type->tp_name);
        return NULL;
    }
    

    由于PyType_Typetype还是指向PyType_Type,所以最后调用到了PyType_Typetp_call中。

    // typeobject.c
    static PyObject *
    type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        PyObject *obj;
    
        if (type->tp_new == NULL) {
            PyErr_Format(PyExc_TypeError,
                     "cannot create '%.100s' instances",
                     type->tp_name);
            return NULL;
        }
    
        obj = type->tp_new(type, args, kwds);
        ......     // 如果是实例对象,则调用__init__进行初始化(这里先忽略)
        return obj;
    }
    

    PyType_Type中的tp_new指向type_new函数,type_new函数才是class对象创建的第一案发现场,这个函数相当复杂,下面是简化后的代码。

    // typeobject.c
    static PyObject * type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
    {
        // metatyp是PyType_Type
        // args中包含了(类名,基类列表,属性表)
        PyObject *name, *bases, *dict;
        static char *kwlist[] = {"name", "bases", "dict", 0};
        PyObject *slots, *tmp, *newslots;
        PyTypeObject *type, *base, *tmptype, *winner;
        PyHeapTypeObject *et;
        PyMemberDef *mp;
        Py_ssize_t i, nbases, nslots, slotoffset;
    
        /* Check arguments: (name, bases, dict) */
        /* 将args中的(类名,基类列表,属性表)
           分别解析到(name, bases, dict)三个变量中 */
        if (!PyArg_ParseTupleAndKeywords(args, kwds, "SO!O!:type", kwlist,
                                         &name,
                                         &PyTuple_Type, &bases,
                                         &PyDict_Type, &dict))
            return NULL;
    
        ......   // 确定最佳metaclass,存储在PyObject *metatype中
        ......   // 确定最佳base,存储在PyObject *base中
    
        /* Allocate the type object */
        // 为class申请内存
        // 尽管PyType_Type为0,但是PyBaseObject_Type的为PyType_GenericAlloc,
        // 在PyType_Ready中被继承了
        // 创建的内存大小为tp_basicsize + tp_itemsize
        type = (PyTypeObject *)metatype->tp_alloc(metatype, nslots);
        if (type == NULL) {
            Py_XDECREF(slots);
            Py_DECREF(bases);
            return NULL;
        }
    
        /* Keep name and slots alive in the extended type object */
        et = (PyHeapTypeObject *)type;
        Py_INCREF(name);
        et->ht_name = name;
        et->ht_slots = slots;
    
        ......
    
        /* Initialize essential fields */
        // 设置PyTypeObject中的各个域
        type->tp_as_number = &et->as_number;
        type->tp_as_sequence = &et->as_sequence;
        type->tp_as_mapping = &et->as_mapping;
        type->tp_as_buffer = &et->as_buffer;
        type->tp_name = PyString_AS_STRING(name);
        if (!type->tp_name) {
            Py_DECREF(bases);
            Py_DECREF(type);
            return NULL;
        }
        if (strlen(type->tp_name) != (size_t)PyString_GET_SIZE(name)) {
            PyErr_SetString(PyExc_ValueError,
                            "type name must not contain null characters");
            Py_DECREF(bases);
            Py_DECREF(type);
            return NULL;
        }
    
        /* Set tp_base and tp_bases */
        // 设置基类和基类列表
        type->tp_bases = bases;
        Py_INCREF(base);
        type->tp_base = base;
    
        /* Initialize tp_dict from passed-in dict */
        // 设置属性表tp_dict
        type->tp_dict = dict = PyDict_Copy(dict);
        if (dict == NULL) {
            Py_DECREF(type);
            return NULL;
        }
    
        ......
    
        /* Special-case __new__: if it's a plain function,
           make it a static function */
        // 如果自定义的函数中重写了__new__,将__new__对应的函数改造成static函数
        tmp = PyDict_GetItemString(dict, "__new__");
        if (tmp != NULL && PyFunction_Check(tmp)) {
            tmp = PyStaticMethod_New(tmp);
            if (tmp == NULL) {
                Py_DECREF(type);
                return NULL;
            }
            if (PyDict_SetItemString(dict, "__new__", tmp) < 0) {
                Py_DECREF(tmp);
                Py_DECREF(type);
                return NULL;
            }
            Py_DECREF(tmp);
        }
    
        /* Add descriptors for custom slots from __slots__, or for __dict__ */
        mp = PyHeapType_GET_MEMBERS(et);
        // 为class对象对应的instance对象设置内存大小信息
        slotoffset = base->tp_basicsize;
        if (slots != NULL) {
            for (i = 0; i < nslots; i++, mp++) {
                mp->name = PyString_AS_STRING(
                    PyTuple_GET_ITEM(slots, i));
                mp->type = T_OBJECT_EX;
                mp->offset = slotoffset;
    
                /* __dict__ and __weakref__ are already filtered out */
                assert(strcmp(mp->name, "__dict__") != 0);
                assert(strcmp(mp->name, "__weakref__") != 0);
    
                slotoffset += sizeof(PyObject *);
            }
        }
        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 */
        // 调用PyType_Ready(type)对class对象进行初始化
        if (PyType_Ready(type) < 0) {
            Py_DECREF(type);
            return NULL;
        }
    
        /* Put the proper slots in place */
        fixup_slot_dispatchers(type);
    
        return (PyObject *)type;
    }
    

    Python虚拟机首先会将类名、基类列表和属性表从tuple对象中解析出来,然后会根据基类列表及传入的metaclass(参数metatype)确定最佳的metaclassbase。对于我们的A来说,最佳metaclass<type 'type'>,最佳base<type 'object'>

    随后Python虚拟机会调用metatype->tp_alloc尝试为所要创建的与A对应的class对象分配内存(PyType_Typetp_allocNULL,但是PyType_Type的父类PyBaseObject_Typetp_allocPyType_GenericAlloc)。最终PyType_GenericAlloc将申请metatype->tp_basicsize+metatype->tp_itemsize大小的内存空间(从PyType_Type的定义中可以看出,实际申请的大小就是sizeof(PyHeapTypeObject)+sizeof(PyMemberDef))

    PyHeapTypeObject实际上就是专门为自定义类准备的结构体。

    接下来就是设置<class A>的各个域。

    然后还会计算与<class A>对应的instance对象的内存大小(也就是说在以后执行a=A()的时候需要给实例对象申请多少内存),这个大小为PyBaseObject_Type->tp_basicsize+8(8=2*sizeof(PyObject *),一个是tp_dictoffset,一个是tp_weaklistoffset)。

    最后,Python虚拟机还会调用PyType_Ready<class A>进行和内置class对象一样的初始化动作。至此,A对应的class对象正式创建完毕。

    用户自定义class对象和内置class对象的内存布局对比

    现在我们对“可调用(callable)”这个概念应该有更深入的认识,只要对象定义了tp_call就可以调用。Pythonclass对象是“调用metaclass对象(<type 'type'>等)创建的,那么调用class对象,就可以得到instance对象?

    0x04 从class对象到instance对象

    # a = A()
    22 LOAD_NAME                1 (A)
    25 CALL_FUNCTION            0
    28 STORE_NAME               2 (a)
    

    22 LOAD_NAME 1 (A)指令是将之前创建的class对象Alocal名字空间入栈到运行时栈;25 CALL_FUNCTION 0用来创建一个instance(调用class对象将创建instance对象);创建完instance对象以后,通过28 STORE_NAME 2 (a)指令将(a, instance)存储到local名字空间中。

    此时的运行时栈

    主要的动作就是在CALL_FUNCTION字节码指令中完成。Python同样会沿着call_function->do_call->PyObject_Call的调用路径。

    调用实际上就是执行对象的type所对应的class对象的tp_call操作,所以在PyObject_CallPython执行引擎会寻找class对象<calss A>type中定义的tp_call操作。<calss A>type<type 'type'>,所以最终将调用tp_call,在PyType_Type.tp_call中又调用了A.tp_newtp_new是继承自PyBaseObject_Type中的tp_newtp_new指向的是object_new)用来创建instance对象。

    创建class对象和创建instance对象的不同之处在于tp_new不同,创建class对象使用的是type_new,创建instance使用的是object_new

    object_new中,会调用A.tp_alloc,也是继承自object,也就是PyType_GenericAlloc。这里会申请24个字节。

    // typeobject.c
    static PyObject *
    type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        PyObject *obj;
    
        if (type->tp_new == NULL) {
            PyErr_Format(PyExc_TypeError,
                         "cannot create '%.100s' instances",
                         type->tp_name);
            return NULL;
        }
    
        obj = type->tp_new(type, args, kwds);
        if (obj != 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(obj->ob_type, type))
                return obj;
            type = obj->ob_type;
            if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_CLASS) &&
                type->tp_init != NULL &&
                type->tp_init(obj, args, kwds) < 0) {
                Py_DECREF(obj);
                obj = NULL;
            }
        }
        return obj;
    }
    

    回到type_call中,如果创建的不是class对象,会尝试进行初始化动作。

    因为是基于<class A>创建的instance对象,所以instance对象的ob_type要设置为<class A>,但是A类对象定义时重写了__init__方法,所以tp_init会指向slotdefs中指定的与__init__对应的slot_tp_init

    // typeobject.c
    static int
    slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
    {
        static PyObject *init_str;
        PyObject *meth = lookup_method(self, "__init__", &init_str);
        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;
    }
    

    在执行slot_tp_init方法时,Python虚拟机会首先通过lookup_methodclass对象及其mro列表中搜索属性__init__对应的操作,然后通过PyObject_Call调用该操作。

    总结

    class对象创建instance对象的两个步骤:

    • instance = class.__new__(class, args, kwds)__new__用来创建创建实例】
    • class.__init__(instance, args, kwds)__init__用来初始化实例】

    0x05 访问instance对象中的属性

    形如x.yx.y()形式的表达式称为“属性引用

    0x06 千变万化的descriptor


    欢迎关注微信公众号(coder0x00)或扫描下方二维码关注,我们将持续搜寻程序员必备基础技能包提供给大家。


    相关文章

      网友评论

          本文标题:【Python】虚拟机中的类机制

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