Python 2.2
开始,Python
中有两套类机制
classic class
(经典类)new style class
(新式类)
0x01 Python中的对象模型
Python 2.2
之前,Python
内置的type
(int
、dict
等)与程序员自己定义的class
并不是完全等同的。
- 用户自定义的类可以被继承,
python
内置type
不能被继承。
Python 2.2
之后的版本,填补了内置type
和用户自定义class
之间的问题,使两者在概念上完全一致,这种统一以后的类型机制就叫做new style class
(新式类)
统一术语:
type
对象:Python
内置的类型
class
对象:Python
程序员定义的类型(<class A>
)
instance
对象(实例对象): 由class
对象创建的实例(<instance a>
)在
Python 2.2
之后,type
和class
已经统一,所以我们用"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
之前,为什么内置的对象不能被继承?
在Python 2.2之后寻找属性的机制比如
int
被继承了以后,子类想调用int.__add__
方法。Python
虚拟机不知道要调用的是PyInt_Type.tp_as_number.nb_add
。Python
虚拟机不具有在type
中寻找某个属性的机制
如上图所示,当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_Ready
对class
对象进行初始化。
PyType_Ready
仅仅是对class
对象进行初始化动作的一部分(类型对象这时已经存在,只进行一些完善工作),它会处理Python
内置类型和用户自定义类型。
- 内置类型
list
对应的class
对象PyList_Type
在Python
启动后已经作为全局对象存在了,需要的仅仅是完善。即对于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_Type
(object
)。承前面讲的,Python
所有class
对象都直接或者间接的以<type 'object'>
作为基类。
然后判断基类是否已经初始化(其中有一个条件是base->tp_dict == NULL
,对应了前面所说的内容),基类没有初始化的话,先初始化基类。
然后设置class
对象的ob_type
信息(就是对象__class__
返回的信息)。这里设置的ob_type
(type->ob_type
)就是metaclass
,实际上,Python
虚拟机是将基类的metaclass
作为子类的metaclass
(type->ob_type = base->ob_type
),基类PyBaseObject_Type
的metaclass
是PyType_Type
,所以到头来PyType_Type
的metaclass
还是自己(PyType_Type
)。
处理基类列表
接下来处理类型的基类列表,因为Python
支持多重继承,所以每一个Python
的class
对象都会有一个基类列表。
// 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_operators
、add_methods
、add_members
、add_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
使用到的宏,最基本的就是TPSLOT
和ETSLOT
(其他宏都是对这两个宏的简单包装)。
TPSLOT
和ETSLOT
的区别在于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_add
在PyTypeObject
中的偏移量,只能计算其在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
了,所以也不能调用),而是包装了slot
的PyObject
对象,称为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
的参数type
(PyWrapperDescr_Type
);d_wrapped
存放着重要的信息,即操作对应的函数指针(比如对于PyList_Type
来说,其tp_dict["__getitem__"].d_wrapped
就是&mp_subscript
);d_base
存储着slot
。
PyWrapperDescr_Type
的tp_call
是wrapperdescr_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))
。如下图说示,
PyHeapTypeObject结构展开式offset(PyHeapTypeObject, mp_subscript)
的偏移肯定大于offset(PyHeapTypeObject, as_mapping)
,所以会先从PyTypeObject
对象中获得as_mapping
的指针p
,然后在p
的基础上进行偏移就可以得到实际的函数地址,偏移量计算方式:delta = offset(PyHeapTypeObject, mp_subscript) - offset(PyHeapTypeObject, as_mapping)
。
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
完成初始化之后的整个布局。
再回到PyType_Ready
中,在通过add_operators
添加了PyTypeObject
对象中定义的一些operator
后,还会通过add_methods
、add_members
和add_getsets
添加在PyTypeObject
中定义的tp_methods
、tp_members
和tp_getset
函数集。这些add_***
的过程与add_operators
类似,不过最后添加到tp_dict
中的descriptor
不再是PyWrapperDescrObject
,而是PyMethodDescrObject
、PyMemberDescrObject
和PyGetSetDescrObject
。
确定MRO
所谓
MRO
,就是Method Resolve Order
(对象的属性解析顺序)
Python
内部在PyType_Ready
中通过mro_internal
函数完成对一个类型的mro
顺序的建立Python
虚拟机将创建一个tuple
对象,在对象中一次存放着一组class
对象,在tuple
中class
对象的顺序就是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
的直接基类的列表。如下图所示:
随后Python
虚拟机从左到右遍历该list
,当访问到list
中的任一个街垒时,如果基类存在mro
列表,则转而访问基类的mro
列表。在访问过程中,不断将所访问到的class
对象放入到D
自身的mro
类表中。
详细步骤:
- 首先获得
D
,D
的mro
列表(tp_mro
)中没有D
,所以放入D
- 获得
C
,D
的mro
列表中没有C
,所以放入C
。发现C
中存在mro
列表,转而访问C
的mro
列表:
- 获得
A
,D
的mro
列表中没有A
,放入A
- 获得
list
,这里需要注意一下,尽管D
的mro
列表中没有list
,但是在后面的B
的mro
列表中出现了list
,那么Python
虚拟机会跳过这里的list
,将list
的处理推迟到处理B
的mro
列表时- 获得
object
,同上- 获得
B
,D
的mro
列表中没有B
,所以放入B
。转而访问B
的mro
列表:
- 获得
list
,这时可以将list
放入D
的mro
列表- 获得
object
,这时可以将object
放入D
的mro
列表最后遍历得到
(<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_NAME
和STORE_NAME
将符号__module__
和全局名字空间中的符号__name__
对应的值(__main__
)关联起来,并放到local
名字空间(PyFrameObject
对象的f_locals
)中。f_locals
需要特别说明一下:函数机制中,f_locals
是被置为NULL
的,局部变量是以一种位置参数的形式存放在f_localsplus
中(运行时栈之前的一段内存区域);而此时的f_locals
是被创建了,然后指向了一个PyDictObject
对象。接着连续执行三个(
LOAD_CONST
、MAKE_FUNCTION
、STORE_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
对象压入到运行时栈中。注意:在执行了
image.pngSTACKADJ(-2)
后,栈顶指针向上移动了2
个位置(也就是最初存储class
名称"A"
的位置)。
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
作为该class
的metaclass
;对于我们的例子中,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_Type
的type
还是指向PyType_Type
,所以最后调用到了PyType_Type
的tp_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
)确定最佳的metaclass
和base
。对于我们的A
来说,最佳metaclass
为<type 'type'>
,最佳base
为<type 'object'>
。随后
Python
虚拟机会调用metatype->tp_alloc
尝试为所要创建的与A对应的class
对象分配内存(PyType_Type
中tp_alloc
为NULL
,但是PyType_Type
的父类PyBaseObject_Type
的tp_alloc
为PyType_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
)。最后,
用户自定义class对象和内置class对象的内存布局对比Python
虚拟机还会调用PyType_Ready
对<class A>
进行和内置class
对象一样的初始化动作。至此,A
对应的class
对象正式创建完毕。
现在我们对“可调用(
callable
)”这个概念应该有更深入的认识,只要对象定义了tp_call
就可以调用。Python
中class
对象是“调用”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
对象A
从local
名字空间入栈到运行时栈;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_Call
中Python
执行引擎会寻找class
对象<calss A>
的type
中定义的tp_call
操作。<calss A>
的type
为<type 'type'>
,所以最终将调用tp_call
,在PyType_Type.tp_call
中又调用了A.tp_new
(tp_new
是继承自PyBaseObject_Type
中的tp_new
,tp_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_method
在class
对象及其mro
列表中搜索属性__init__
对应的操作,然后通过PyObject_Call
调用该操作。
总结
从class
对象创建instance
对象的两个步骤:
-
instance = class.__new__(class, args, kwds)
【__new__
用来创建创建实例】 -
class.__init__(instance, args, kwds)
【__init__
用来初始化实例】
0x05 访问instance对象中的属性
形如
x.y
或x.y()
形式的表达式称为“属性引用”
0x06 千变万化的descriptor
欢迎关注微信公众号(coder0x00)或扫描下方二维码关注,我们将持续搜寻程序员必备基础技能包提供给大家。
网友评论