【CPython3.6源码分析】Python 类机制

前言

PyObject/PyObjectType,我们已经看到过,PyObject、PyTypeObject、PyType_Type、PyBaseObject_Type。

下面通过几个例子来具体描述关系:

  • 实例,通过isinstance()方法,检查其 ob_type 是否一致。
  • 子类,通过issubclass()方法,检查其 bases 是否存在继承关系。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>> isinstance(object, type)
    True # object.__class__ == <class 'type'>
    >>> issubclass(object, type)
    False # 注意:object.__base__ == None

    >>> isinstance(type, object)
    True # 注意:type.__class__ == <class 'type'>
    >>> issubclass(type, object)
    True # type.__base__ == <class 'object'>

这里需要特别注意,虽然 type.ob_type 并不指向 object,但isinstance(type, object)的结果仍然是True!

事实上,isinstacne(anything, object) 都是 True。issubclass(AnyClass, object)也都是True。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A:
pass
class B(A):
pass

print(isinstance(B, A)) # False, B的 ob_type 不指向 A
print(isinstance(B, object)) # True, 永真式
print(isinstance(B, type)) # True, B的 ob_type 指向 PyType_Type
print(isinstance(B(), B)) # True, 实例的 ob_type 指向类,没毛病
print(isinstance(B(), A)) # True, 实例的类的继承关系
print(isinstance(B(), object)) # True, 新式类都继承自 object
print(isinstance(B(), type)) # False, 这里不要搞错了

print(issubclass(B, A)) # True, B继承 A
print(issubclass(B, object)) # True, 永真式
print(issubclass(B, type)) # False, object 并不继承自 type
print(issubclass(B(), B)) # TypeError: 实例无法判断继承关系

总结一下:

  1. 任何对象,都有一个ob_type,可通过__class__属性得到
  2. 任何实例对象的 ob_type,都是 class 对象
  3. 任何 class 对象的 ob_type 都是 metaclass 对象
  4. 任何 class 对象,都继承自 ,包括
  5. 默认的 metaclass 对象是 对应 PyType_Type
  6. 对应 PyBaseObject_Type

PyType_Ready

处理基类和type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// typeobject.c.4913
int PyType_Ready(PyTypeObject *type)
{
PyObject *dict, *bases;
PyTypeObject *base;
Py_ssize_t i, n;

/* 初始化完成的判断依据 */
if (type->tp_flags & Py_TPFLAGS_READY) {
assert(type->tp_dict != NULL);
return 0;
}
type->tp_flags |= Py_TPFLAGS_READYING; /* Reading */

/* 除了Object自身, 其余 tp_base 都设置成 Object
class对象 基类信息
PyBaseObject_Type NULL 不存在
PyType_Type NULL 不存在,设置为 object
PyLong_Type NULL 不存在,设置为 object
PyBool_Type &PyLong_Type 存在,不修改
*/
base = type->tp_base;
if (base == NULL && type != &PyBaseObject_Type) {
base = type->tp_base = &PyBaseObject_Type;
Py_INCREF(base);
}
if (base != NULL && base->tp_dict == NULL) {
if (PyType_Ready(base) < 0)
// 尝试优先初始化基类,最终第一个处理 Object
goto error;
}

/* 设置其 type 为基类的 type
自定义 class A: -> A.base = object
-> A.type = object.type = PyType_Type
*/
if (Py_TYPE(type) == NULL && base != NULL)
Py_TYPE(type) = Py_TYPE(base);

/* 初始化 tp_bases 属性 */
bases = type->tp_bases;
if (bases == NULL) {
if (base == NULL)
/* object.__bases__ = () */
bases = PyTuple_New(0);
else
/* type.__bases__ = (object,)*/
bases = PyTuple_Pack(1, base);
type->tp_bases = bases;
}

...
}

从上面的源码中,能清楚的看到基类object、元类type的关系。哇,从此以后再也不会烦恼type和obejct的关系了。

递归到底层,优先执行PyType_Ready(&PyBaseObject_Type)

处理 tp_dict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int PyType_Ready(PyTypeObject *type)
{
...
/* Initialize 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 */
if (add_operators(type) < 0)
goto error;
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;
}
...
}

如上,在这一阶段,将各种方法放入type->tp_dict中。

add_operators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* This function is called by PyType_Ready() to populate the type's
dictionary with method descriptors for function slots. For each
function slot (like tp_repr) that's defined in the type, one or more
corresponding descriptors are added in the type's tp_dict dictionary
under the appropriate name (like __repr__). Some function slots
cause more than one descriptor to be added (for example, the nb_add
slot adds both __add__ and __radd__ descriptors) and some function
slots compete for the same descriptor (for example both sq_item and
mp_subscript generate a __getitem__ descriptor).

In the latter case, the first slotdef entry encountered wins. Since
slotdef entries are sorted by the offset of the slot in the
PyHeapTypeObject, this gives us some control over disambiguating
between competing slots: the members of PyHeapTypeObject are listed
from most general to least general, so the most general slot is
preferred. In particular, because as_mapping comes before as_sequence,
for a type that defines both mp_subscript and sq_item, mp_subscript
wins.

This only adds new descriptors and doesn't overwrite entries in
tp_dict that were previously defined. The descriptors contain a
reference to the C function they must call, so that it's safe if they
are copied into a subtype's __dict__ and the subtype has a different
C function in its slot -- calling the method defined by the
descriptor will call the C function that was used to create it,
rather than the C function present in the slot when it is called.
(This is important because a subtype may have a C function in the
slot that calls the method from the dictionary, and we want to avoid
infinite recursion here.) */

老套路,开局一段注释。从注释中,我们能提取到一些信息:

  • add_operators 的作用是,将方法 slot 的封装后填充到 type->to_dict
  • 定义在 type 中的每个方法,都有一个名字与其对应
  • 名字与方法之间的关系不是一一对应
  • 对于同样名字不同方法,选择 offset 较小者
  • add_operators 只会添加新的方法描述,而不会覆盖已有的
  • slot 中含有一个可调用的 C 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// typeobject.c.7179
static int add_operators(PyTypeObject *type)
{
PyObject *dict = type->tp_dict;
slotdef *p;
PyObject *descr;
void **ptr;

init_slotdefs();
for (p = slotdefs; p->name; p++) {
ptr = slotptr(type, p->offset);
...
}
if (type->tp_new != NULL) {
if (add_tp_new_wrapper(type) < 0)
return -1;
}
return 0;
}

emmm,注释那么长,代码这么短。注释中谈到的slot果然出现了,并且还是初始化slotdef。既然如此,我们就先看看 slotdef 为何方神圣。

slotdef

1
2
3
4
5
6
7
8
9
10
11
12
13
// typeobject.c.6572
typedef struct wrapperbase slotdef;

// descrobject.h.26
struct wrapperbase {
char *name; /* 操作名称 __add__ */
int offset; /* 函数在 PyHeapTypeObject 中的偏移量 */
void *function; /* 函数指针 */
wrapperfunc wrapper;
char *doc;
int flags;
PyObject *name_strobj;
};

在 Python 内部,solt 可以视为表示 PyTypeObject 中定义的操作,一个操作对应一个 slot。从结构体可以看出,不仅含有函数的指针,还有对应的名字以及一个偏移量,这些整体,作为一个描述符 slot。那么,这个offset是什么鬼?

offsetof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// typeobject.c.6541
#define TPSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
{NAME, offsetof(PyTypeObject, SLOT), (void *)(FUNCTION), WRAPPER, \
PyDoc_STR(DOC)}
#define ETSLOT(NAME, SLOT, FUNCTION, WRAPPER, DOC) \
{NAME, offsetof(PyHeapTypeObject, SLOT), (void *)(FUNCTION), WRAPPER, \
PyDoc_STR(DOC)}

// C标准库 stddef.h
size_t offsetof(type, member-designator);
/*
参数
type -- class 类型
member-designator -- class 类型的成员指示器。
返回值
表示 type 中成员的偏移量。
*/

使用标准库 stddef.h中的offsetof方法,获取结构体中成员的偏移量。TPSLOTETSLOT的唯一区别是,指定的结构体不一样。很好,PyHeapTypeObject又是什么东西?

PyHeapTypeObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct _heaptypeobject {
PyTypeObject ht_type;
PyAsyncMethods as_async;
PyNumberMethods as_number;
PyMappingMethods as_mapping;
PySequenceMethods as_sequence;
PyBufferProcs as_buffer;
PyObject *ht_name, *ht_slots, *ht_qualname;
struct _dictkeysobject *ht_cached_keys;
} PyHeapTypeObject;

typedef struct _typeobject {
...
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence; /* 注意:顺序不一致 */
PyMappingMethods *tp_as_mapping;
...
} PyTypeObject;

观察发现,PyHeapTypeObject 的第一个元素就是 PyTypeObject。意味着,如果一个方法在 PyTypeObject 中,那么通过TPSLOT计算出的偏移量,其实跟该方法在PyHeapTypeObject中的偏移值相等。那么问题来了,这个偏移量到底有什么用?

slotdefs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// object.h
typedef struct {
lenfunc sq_length;
binaryfunc sq_concat;
ssizeargfunc sq_repeat;
ssizeargfunc sq_item;
...
} PySequenceMethods;

// typeobject.c
typedef struct wrapperbase slotdef;

#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)

static slotdef slotdefs[] = {
// 相同名字,不同操作
MPSLOT("__getitem__", mp_subscript, slot_mp_subscript, wrap_binaryfunc,
"__getitem__($self, key, /)\n--\n\nReturn self[key]."),
SQSLOT("__getitem__", sq_item, slot_sq_item, wrap_sq_item,
"__getitem__($self, key, /)\n--\n\nReturn self[key]."),
/* wrapperbase = {
name = "__getitem__",
offset = offsetof(PyHeapTypeObject, as_sequence.slot_sq_item),
function = slot_sq_item,
wrapperfunc = wrap_sq_item,
doc = "__getitem__($self, key, /)\n--\n\nReturn self[key].",
flags = None,
name_strobj = None
}
*/
// 不同名字,相同操作
BINSLOT("__add__", nb_add, slot_nb_add, "+"),
RBINSLOT("__radd__", nb_add, slot_nb_add, "+"),
...
{NULL}
};

如上,Python 定义了一个slotdef数组。注意,其中NAME和FUNCTION并不是一一对应的。根据宏的不同,选择不同的函数簇,那么计算出的偏移量肯定也是不同的。在填充tp_dict的过程中,出现相同名字例如__getitem__时,会选择offset较小的那个。所以,offset 实际上是为了对存在相同方法名的,各个函数簇之间的操作优先级进行排序。

是了,跟 add_operators 中的注释内容一模一样。好了,让我们回到最初的起点 add_operators。

add_operators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// typeobject.c.7179
static int add_operators(PyTypeObject *type)
{
slotdef *p;
// 初始化
init_slotdefs();
// 循环处理 descriptor
for (p = slotdefs; p->name; p++) {
ptr = slotptr(type, p->offset);
...
}
// 处理 new
if (type->tp_new != NULL) {
if (add_tp_new_wrapper(type) < 0)
return -1;
}
return 0;
}

如上,三步走。

init_slotdefs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// typeobject.c.6945
static int slotdefs_initialized = 0;
static void init_slotdefs(void)
{
slotdef *p;

if (slotdefs_initialized)
return;
for (p = slotdefs; p->name; p++) {
p->name_strobj = PyUnicode_InternFromString(p->name);
if (!p->name_strobj || !PyUnicode_CHECK_INTERNED(p->name_strobj))
Py_FatalError("Out of memory interning slotdef names");
}
slotdefs_initialized = 1;
}

代码很简单,注意其中的INTERNED。想起来什么没有?根据NAME字符串__str__生成一个UnicodeObject,同时放入 interned 字典中。忘记的不妨看看Unicode 共享机制

循环处理 descriptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
PyObject *dict = type->tp_dict;
slotdef *p;
PyObject *descr;
void **ptr;
for (p = slotdefs; p->name; p++) {
// 忽略未指定 warpper 的 func
if (p->wrapper == NULL)
continue;
// 获取实际指针
ptr = slotptr(type, p->offset);
if (!ptr || !*ptr)
continue;
// 此处,忽略已经存在的,对应上文的注释
if (PyDict_GetItem(dict, p->name_strobj))
continue;
if (*ptr == (void *)PyObject_HashNotImplemented) {
// 不可hash对象
if (PyDict_SetItem(dict, p->name_strobj, Py_None) < 0)
return -1;
}
else {
// 创建 descriptor
descr = PyDescr_NewWrapper(type, p, *ptr);
// 将 descriptor 作为 Value 放入字典
if (PyDict_SetItem(dict, p->name_strobj, descr) < 0) {
Py_DECREF(descr);
return -1;
}
Py_DECREF(descr);
}
}

如上,在循环的过程中,会先通过 slotptr获取到实际的 slotdef 指针。然后为每个 slotdef 创建一个 descr,最后实际放入字典的是 descr 而不是 slot,这是为什么?

1
2
3
4
struct wrapperbase {
void *function; /* 函数指针 */
wrapperfunc wrapper;
} slotdef;

回想一下关于 slotdef 的定义,似乎这就是一个单纯的C结构体。所以,必须对其进行包装,生成一个 PyObejct,才能放入字典中。

PyWrapperDescrObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// descobject.h
typedef struct {
PyObject_HEAD
PyTypeObject *d_type;
PyObject *d_name;
PyObject *d_qualname;
} PyDescrObject;

typedef struct {
PyDescrObject d_common;
struct wrapperbase *d_base;
void *d_wrapped; /* This can be any function pointer */
} PyWrapperDescrObject;

PyObject *
PyDescr_NewWrapper(PyTypeObject *type, struct wrapperbase *base, void *wrapped)
{
PyWrapperDescrObject *descr;
descr = (PyWrapperDescrObject *)descr_new(&PyWrapperDescr_Type,
type, base->name);
descr->d_base = base; /* 存放 slotdef */
descr->d_wrapped = wrapped; /* 存放 实际的函数指针 */
return (PyObject *)descr;
}

static PyDescrObject *
descr_new(PyTypeObject *descrtype, PyTypeObject *type, const char *name)
{
PyDescrObject *descr;
// 指定了 type 为 PyWrapperDescr_Type
descr = (PyDescrObject *)PyType_GenericAlloc(descrtype, 0);
Py_XINCREF(type);
descr->d_type = type;
descr->d_name = PyUnicode_InternFromString(name);
descr->d_qualname = NULL;
return descr;
}

如上,代码很简单。PyWrapperDescrObject 的 type 为 PyWrapperDescr_Type,因此调用 tp_dict[“xx“],将会调用PyWrapperDescr_Type.__call__

slot_tp_repr

1
2
3
4
5
6
static slotdef slotdefs[] = {
TPSLOT("__repr__", tp_repr, slot_tp_repr, wrap_unaryfunc,
TPSLOT("__str__", tp_str, slot_tp_str, wrap_unaryfunc,
"__str__($self, /)\n--\n\nReturn str(self)."),
...
}

在上面的slotdefs介绍中,其实还存在另外一种模式。想想 Python 中自定义__str__方法,按照循环处理中的逻辑,已经存在的方法,不会被替换。那么用户自定义的方法如何实现的?

1
2
3
4
5
6
7
8
9
10
11
12
static PyObject * slot_tp_str(PyObject *self)
{
PyObject *func, *res;
_Py_IDENTIFIER(__str__);

func = lookup_method(self, &PyId___str__);
if (func == NULL)
return NULL;
res = PyEval_CallObject(func, NULL);
Py_DECREF(func);
return res;
}

如上,在最终调用__str__方法时,表面调用的是slot_tp_str,内部却通过lookup_method,找到用户自定义的__str__,执行。具体内容留在 Python 自定义类 讲述。

PyType_Ready-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* Add type-specific descriptors to tp_dict */
if (add_operators(type) < 0)
goto error;
/* 添加其他函数簇:add_* */

/* 创建 MRO:mro_internal */

/* 继承属性、方法 :
从 base 继承 flags
从 mro 继承 slotdef 方法
从 base 继承 tp_as_number 等函数簇
*/


/* 遍历 tp_mro,验证所有 class 都应是静态分配的 */

/* Sanity check for tp_free:
如果可以被GC,那么 tp_free 必然不为NULL,且是可被调用
*/

/* if the type dictionary doesn't contain a __doc__
set it from the tp_doc slot.
*/

/* __hash__ 是不继承的
if tp_has is NULL and '__hash__' not in tp_dict:
tp_dict['__hash__'] = None
设置 type 为不可 hash
*/

/* 遍历 bases,为 base 添加子类:
add_subclass((PyTypeObject *)b, type)
*/

/* All done -- set the ready flag
type->tp_flags & ~Py_TPFLAGS_READYING
type->tp_flags | Py_TPFLAGS_READY
*/
return 0;

add_*

最终,在 add_operators 完成后的布局如下:
add_operators完成后的PyList_Type

其中,PyList_Type.tp_as_mapping等都是在编译时确定,而 tp_dict 是Python 运行时初始化建立的。

1
2
3
4
5
6
7
8
9
10
11
12
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;
}

PyType_Ready 在通过 add_operators 添加了 PyTypeObject 中定义的方法后,再将定义的函数簇添加到 tp_dict 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char *ml_doc; /* The __doc__ attribute, or NULL */
} PyMethodDef;

// typeobject.c.4581
static int
add_methods(PyTypeObject *type, PyMethodDef *meth)
{
PyObject *dict = type->tp_dict;
for (; meth->ml_name != NULL; meth++) {
PyObject *descr;
if (meth->ml_flags & METH_CLASS) {
descr = PyDescr_NewClassMethod(type, meth);
}
else if (meth->ml_flags & METH_STATIC) {
PyObject *cfunc = PyCFunction_NewEx(meth, (PyObject*)type, NULL);
descr = PyStaticMethod_New(cfunc);
Py_DECREF(cfunc);
}
else {
descr = PyDescr_NewMethod(type, meth);
}
err = PyDict_SetItemString(dict, meth->ml_name, descr);
}
return 0;
}

PyObject *
PyDescr_NewMethod(PyTypeObject *type, PyMethodDef *method)
{
descr = (PyMethodDescrObject *)descr_new(&PyMethodDescr_Type,
type, method->ml_name);}
}
PyObject *
PyDescr_NewClassMethod(PyTypeObject *type, PyMethodDef *method)
{
descr = (PyMethodDescrObject *)descr_new(&PyClassMethodDescr_Type,
type, method->ml_name);
}

如上,在 add_method 的过程中,判断方法类型,在生成对应的 DescrObject,但最终都当做 PyMethodDescrObject 使用。同理,tp_members,add_getset放入tp_dict 的都是对应的DescrObject

mro_internal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* Calculate method resolution order */
if (mro_internal(type, NULL) < 0)
goto error;

/* 计算并赋值 type->tp_mro
- 正常返回1
- 自定义了 mro(),而导致 tp_mro 发生更改时,返回 0
- 错误返回 -1
*/

static int
mro_internal(PyTypeObject *type, PyObject **p_old_mro)
{
PyObject *new_mro, *old_mro;
new_mro = mro_invoke(type);
type->tp_mro = new_mro;
}

/* 在 mro_invoke() 中:
if type!=PyType_Type:
查找 自定义的 mro_meth
mro_reslt = mero_meth()
new_mro = PySequence_Tuple(mro_result);
return new_mro
*/

mro_internal中实现了 MRO 的创建,可以看到,MRO 是一个元组对象。其中的元素都是 class对象,方

Inherit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* inherit_special:
视情况拷贝,tp_clear、tp_traverse、tp_itemsize等
根据base,继承 flag, 更改 tp_flags
*/
if (type->tp_base != NULL)
inherit_special(type, type->tp_base);

/* Initialize tp_dict properly */
bases = type->tp_mro;
n = PyTuple_GET_SIZE(bases);
for (i = 1; i < n; i++) {
PyObject *b = PyTuple_GET_ITEM(bases, i);
if (PyType_Check(b))
inherit_slots(type, (PyTypeObject *)b);
}

base = type->tp_base;
if (base != NULL) {
if (type->tp_as_async == NULL)
type->tp_as_async = base->tp_as_async;

如上,从 base 继承一些属性,并从 mro 中,继承 方法。

总结

PyType_Ready 对 type 对象进行初始化操作:

  • 处理基类:除了Object自身, 其余 tp_base 都设置成 Object
  • 处理 type:if type.type is NULL: type.type = base.type
  • 处理 bases:object.bases = (); otherClass.bases = (object,)
  • 填充 tp_dict:遍历 slotdefs,放入 DescrObject
  • 创建 mro:调用 mro_internal
  • 从base、mro、继承 flags 方法等
  • 为基类添加注册子类:add_subclass