python源码剖析No.3--python中一切皆对象

面向对象理论中“类”和“对象”这两个重要概念在python中均以对象的形式存在。

“类”是一种对象,称为类型对象;“类”实例化出来的“对象”也是对象,称为实例对象。根据上节的分析,可以根据对象的特点进一步划分:

所以对象在python长得什么“妖怪模样”?

由于python是由C语言实现的,因此python对象在C语言层面应该是一个结构体。不同类型的对象,数据以及行为均可能存在差异,这是对象的个性,对象也是存在一些共性的,比如每个对象都需要有一个引用计数,用于实现垃圾回收机制。

关于对象的具体模样,需要在源码中一窥究竟

python doc about object

PyObject,对象的基石

在python内部,定长对象都是由PyObject结构体表示,对象引用则是利用指针 PyObject * 。关于PyObject结构体的定义

位于源码位置/include/object.h

1
2
3
4
5
6
7
8
9
10
/* Nothing is actually declared to be a PyObject, but every pointer to
* a Python object can be cast to a PyObject*. This is inheritance built
* by hand. Similarly every pointer to a variable-size Python object can,
* in addition, be cast to PyVarObject*.
*/
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;

PyObject结构体的成员有三:

  1. 宏定义_PyObject_HEAD_EXTRA

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #ifdef Py_TRACE_REFS
    /* Define pointers to support a doubly-linked list of all live heap objects. */
    #define _PyObject_HEAD_EXTRA \
    struct _object *_ob_next; \
    struct _object *_ob_prev;

    #define _PyObject_EXTRA_INIT 0, 0,

    #else
    #define _PyObject_HEAD_EXTRA
    #define _PyObject_EXTRA_INIT
    #endif

    若Py_TRACE_REFS有定义,则宏_PyObject_HEAD_EXTRA展开为两个_object指针,注释:这两个指针构成双向链表由于串起所有堆中活跃对象,一般不启用,不做过多介绍

  2. 引用计数(ob_refcnt)

    当对象在其他位置被引用,该值+1,在某处引用解除,该值-1,当引用计数 = 0,则由回收机制进行资源回收。

  3. 类型指针(ob_type)

    指明当前对象所属的类型,由此指向的类型决定当前对象的描述,数据,以及对象可进行的操作

变长对象是在定长对象PyObject基础上扩充的PyVarObject定义(/include/object.h)

1
2
3
4
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

结构体PyVarObject成员有二:

  1. 第一个成员ob_base是上文提及的PyObject类型
  2. 相比PyObject多了一个成员ob_size,用于说明串联的元素个数ob_size,比如int类型,需要串联几个32位整型进行数值表达

python根据对象的特点(是否定长)的需要来包含相应的头部PyObject 、 PyVarObject。为此,为这两个头部准备了宏定义,方便其他对象使用

1
2
#define PyObject_HEAD          PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;
定长对象举例–float

对于定长对象 float对应的结构体PyFloatObject(定义位置:/include/floatobject.h)

1
2
3
4
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
变长对象举例–list

变长对象list对应的结构体PyListObject(定义位置:/include/listobject.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef Py_LIMITED_API
typedef struct {
PyObject_VAR_HEAD
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;

/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
* Invariants:
* 0 <= ob_size <= allocated
* len(list) == ob_size
* ob_item == NULL implies ob_size == allocated == 0
* list.sort() temporarily sets allocated to -1 to detect mutations.
*
* Items must normally not be NULL, except during construction when
* the list is not yet visible outside the function that builds it.
*/
Py_ssize_t allocated;
} PyListObject;
#endif

根据注释内容对ob_item的描述,ob_item包含的空间是'allocated' elementsallocated element中已使用的部分由ob_size进行描述,ob_size的位置是可变对象头部PyObject_VAR_HEAD中的字段,这里再贴一遍PyObject_VAR_HEAD的结构体:

1
2
3
4
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

因此,对于变长对象list的示意图:

可以看到,对于变长对象list而言,在结构体中会设置分配的空间allocated大小,随后若list空间不足需求,会有相应的扩充方法,这部分后续学习到list源码在做补充吧。


对象头部的初始化

python为了方便对对象头部初始化做了相关宏定义,这里做下拆解进行说明

对于定长对象

代码位置(/include/object.h)

1
2
3
#define PyObject_HEAD_INIT(type)        \
{ _PyObject_EXTRA_INIT \
1, type },

PyObject_HEAD_INIT一般用于定长对象头部初始化,对于定长对象头部:

1
2
3
4
5
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;

可以看到初始化操作设置_PyObject_HEAD_EXTRA使用宏_PyObject_EXTRA_INIT 由于_PyObject_HEAD_EXTRA一般不启用,不做深究。

接着设置定长对象的引用计数 = 1,并根据定长对象的类型完成ob_type类型的设置。

变长对象

接着看变长对象初始化做了什么

1
2
#define PyVarObject_HEAD_INIT(type, size)       \
{ PyObject_HEAD_INIT(type) size },

同样的,这里再po一遍变长对象头部:

1
2
3
4
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

ob_base前文提到是定长对象头部的宏定义来的,为了方便引用。相比定长对象头部,只是多了一个成员ob_size。

对于变长对象头部中的定长对象头部,做的初始化和定长对象头部初始化一致,设置对象引用计数 = 1,完成对象类型ob_type的设置,最后对变长对象头部独有的长度size设置。


PyTypeObject,类型对象的基石

这部分关乎上文提到的PyObject(PyVarObject)头部中的type类型设置。

经过上述PyObject对象相关学习,知道对于对象头中一些字段是所有对象共有的,引用计数、类型指针..也有一些值区别字段,比如变长对象中指示元素格式的ob_size

目前对于python对象经过前面的学习,知道了int和 list二者都是变长对象,先看下面代码

1
2
3
4
5
6
7
8
9
a = []
print(sys.getsizeof(a))

b = 1
print(sys.getsizeof(b))

#-------------output--------------#
56
28

可以看到,同为变长对象的int list二者在分配内存有所差异的,再比如对于list对象,我们可以使用append追加元素的函数,而对于int类型则不行,也就是创建对象初始化时做的比我们想的要多,那么依据又是什么呢? –答案已经显而易见的,根据实际创建的对象所对应的类型而言,我们注意到在对象头部初始化:

1
2
3
#define PyObject_HEAD_INIT(type)        \
{ _PyObject_EXTRA_INIT \
1, type }, //关键在于type

顺藤摸瓜,找到PyObject头部中查看type对应的结构体:**_typeobject**

找到定义的位置(/Doc/includes/typestruct.h):

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

/* Methods to implement standard operations */

destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
reprfunc tp_repr;

/* Method suites for standard classes */

PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;

/* More standard operations (here for binary compatibility) */

hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;

/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;

/* Flags to define presence of optional/expanded features */
unsigned long tp_flags;

const char *tp_doc; /* Documentation string */

/* call function for all accessible objects */
traverseproc tp_traverse;

/* delete references to contained objects */
inquiry tp_clear;

/* rich comparisons */
richcmpfunc tp_richcompare;

/* weak reference enabler */
Py_ssize_t tp_weaklistoffset;

/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;

/* Attribute descriptor and subclassing stuff */
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
struct _typeobject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
descrsetfunc tp_descr_set;
Py_ssize_t tp_dictoffset;
initproc tp_init;
allocfunc tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache;
PyObject *tp_subclasses;
PyObject *tp_weaklist;
destructor tp_del;

/* Type attribute cache version tag. Added in version 2.6 */
unsigned int tp_version_tag;

destructor tp_finalize;

} PyTypeObject;

从结构体的定义来看,关键信息在于:

  • PyTypeObject第一个成员为PyObject_VAR_HEAD,可见PyTypeObject是一个变长对象
  • tp_name成员,类型名称,可用于打印需要
  • 事关内存分配的 tp_basicsize, tp_itemsize
  • 类型的继承信息,例如tp_base指向基类对象
  • 类型的子类信息,tp_subclasses
  • 类型允许的操作信息:descrgetfunc tp_descr_get;等等

PyTypeObject类型就是类型对象在python中的表现形式,对应着面向对象中“类”的概念

PyList_Type

实例进行探讨,以list类型为例:

1
2
3
4
5
6
7
8
9
10
var = ['a','b']
print(type(var))
var.append('c')
var
var2 = [1,2,3]
print(type(var2))

#---------output------------#
<class 'list'>
<class 'list'>

list为列表类型对象,在系统中只有唯一一个,保存着所有列表实例出来的实例对象的元信息(共性部分)。

由于list类型对象全局唯一,在C语言层面作为一个全局静态变量静态定义即可。python也是怎么做的

顺藤摸瓜:

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
PyTypeObject PyList_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) //变长对象头部初始化
"list", //名称
sizeof(PyListObject), //分配的内存大小
0,
(destructor)list_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
(reprfunc)list_repr, /* tp_repr */
0, /* tp_as_number */
&list_as_sequence, /* tp_as_sequence */
&list_as_mapping, /* tp_as_mapping */
PyObject_HashNotImplemented, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS, /* tp_flags */
list___init____doc__, /* tp_doc */
(traverseproc)list_traverse, /* tp_traverse */
(inquiry)_list_clear, /* tp_clear */
list_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
list_iter, /* tp_iter */
0, /* tp_iternext */
list_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)list___init__, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
PyType_GenericNew, /* tp_new */
PyObject_GC_Del, /* tp_free */
};

至此可以看到一些list类型对象的一些个性行为。比如list_methods,跟随:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
tatic PyMethodDef list_methods[] = {
{"__getitem__", (PyCFunction)list_subscript, METH_O|METH_COEXIST, "x.__getitem__(y) <==> x[y]"},
LIST___REVERSED___METHODDEF
LIST___SIZEOF___METHODDEF
LIST_CLEAR_METHODDEF
LIST_COPY_METHODDEF
LIST_APPEND_METHODDEF
LIST_INSERT_METHODDEF
LIST_EXTEND_METHODDEF
LIST_POP_METHODDEF
LIST_REMOVE_METHODDEF
LIST_INDEX_METHODDEF
LIST_COUNT_METHODDEF
LIST_REVERSE_METHODDEF
LIST_SORT_METHODDEF
{NULL, NULL} /* sentinel */
};

从PyList_Type的构成可以看到,用于初始化list类型对象头部的语句是:PyVarObject_HEAD_INIT(&PyType_Type, 0)可以看到使用&PyType_Type初始化变长对象头部中的定长对象头部分(因为这是对象共有部分)。

PyType_Type

前面提到。

进一步探讨PyType_Type,跟踪,来到/Objects/typeobject.c,实际上这个结构体就是类型变量的结构体

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
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
(destructor)type_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
(reprfunc)type_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
(ternaryfunc)type_call, /* tp_call */
0, /* tp_str */
(getattrofunc)type_getattro, /* tp_getattro */
(setattrofunc)type_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS, /* tp_flags */
type_doc, /* tp_doc */
(traverseproc)type_traverse, /* tp_traverse */
(inquiry)type_clear, /* tp_clear */
0, /* tp_richcompare */
offsetof(PyTypeObject, tp_weaklist), /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
type_methods, /* tp_methods */
type_members, /* tp_members */
type_getsets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
offsetof(PyTypeObject, tp_dict), /* tp_dictoffset */
type_init, /* tp_init */
0, /* tp_alloc */
type_new, /* tp_new */
PyObject_GC_Del, /* tp_free */
(inquiry)type_is_gc, /* tp_is_gc */
};

python内建类型和自定义类对应的PyTypeObject对象都是通过PyType_Type创建的。PyType_Type在python类型机制中是一个至关重要的对象,他是所有类型的类型,称为元类型,类型也是对象,其类型指向自身,这一点可以在上述代码中PyVarObject_HEAD_INIT(&PyType_Type, 0)得到印证,因为将其自身作为ob_type的值

PyBaseObject_Type,类型之基

object是另一个特殊的类,他是所有类型的基类,在所有类型的结构体中由字段tp_base指明

PyList_Type.tp_base:

发现相应的字段并没有进行初始化。根据理论,这个值不应该为0,因为他是由一个基类派生出来的。因此应该是在源码的某处完成了该字段的初始化,查找PyList_Type发现几处出现,这里需要记住一些源码阅读的技巧:

尽管查找结果有许多处,但首先可以关注文件名,进一步关注内容,从而定位到/Objects/object.c中的代码:

1
2
3
4
5
6
7
8
void
_Py_ReadyTypes(void)
{
//略,都是些形式差不多的其他类型判断语句
if (PyType_Ready(&PyList_Type) < 0)
Py_FatalError("Can't initialize list type");
//略
}

原来是需要进一步调用PyType_Ready完成最终的初始化是python/C API,文档相关描述:

int PyType_Ready(PyTypeObject *type)

Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a type’s base class. Return 0 on success, or return -1 and sets an exception on error.

跟踪该函数,来到/Objects/tyoeobject.h中找到函数的定义:代码有点多,多是一些其他字段的设置,这里先关注类型基类的设置部分

1
2
3
4
5
6
7
8
9
10
11
12
int PyType_Ready(PyTypeObject *type)
{
//...
/* Initialize tp_base (defaults to BaseObject unless that's us) */
base = type->tp_base;
if (base == NULL && type != &PyBaseObject_Type) {
base = type->tp_base = &PyBaseObject_Type;
Py_INCREF(base);
//...
}

}

当tp_base为null且类型不是PyBaseObject_Type时,设置基类指向PyBaseObject_Type。即设置所有非PyBaseObject_Type类型的类型中的tp_base指向PyBaseObject_Type(好绕…),不管如何,大致意思是PyBaseObject_Type是所有类型的基类型,跟踪其定义,来到/Objects/tyoeobject.c

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
PyTypeObject PyBaseObject_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"object", /* tp_name */
sizeof(PyObject), /* tp_basicsize */
0, /* tp_itemsize */
object_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
object_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)_Py_HashPointer, /* tp_hash */
0, /* tp_call */
object_str, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
PyObject_GenericSetAttr, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
PyDoc_STR("object()\n--\n\nThe most base type"), /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
object_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
object_methods, /* tp_methods */
0, /* tp_members */
object_getsets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
object_init, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
object_new, /* tp_new */
PyObject_Del, /* tp_free */
};

其中 0, /* tp_base */可以看到类型基类的PyBaseObject_Type其基类为空,这是因为所有基类到这里就是终点,需要一个出口,否则陷入定义的死循环。

可以总结出来的是:

  • 所有类型的元类型是PyType_Type(类型的ob_type指向PyType_Type),

  • 所有类型的基类是PyBaseObject_Type(非PyBaseObject_Type类型的tp_base指向PyBaseObject_Type)

最后补一个十分详细的PyObject 个字段的描述:Link

Author: Victory+
Link: https://cvjark.github.io/2022/05/06/python源码剖析-python中一切皆对象/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.