美文网首页
【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