面向对象理论中 “ 类 ”和“ 对象 ” 这两个重要概念,在 Python 内部均 以对象的形式存在。 “类”是一种对象,称为 类型对象 ;“类”实例化生成的“对象”也是对象,称为 实例对象 。
根据对象不同特点还可进一步分类:
类别 | 特点 |
---|---|
可变对象 | 对象创建后可以修改 |
不可变对象 | 对象创建后不能修改 |
定长对象 | 对象大小固定 |
变长对象 | 对象大小不固定 |
那么,对象在 Python 内部到底长啥样呢? 由于 Python 是由 C 语言实现的,因此 Python 对象在 C 语言层面应该是一个 结构体 ,组织对象占用的内存。 不同类型的对象,数据及行为均可能不同,因此可以大胆猜测:不同类型的对象由不同的结构体表示。 对象也有一些共性,比如每个对象都需要有一个 引用计数 ,用于实现 垃圾回收机制 。 因此,还可以进一步猜测:表示对象的结构体有一个 公共头部 。
PyObject,对象的基石#
在 Python 内部,对象都由 PyObject 结构体表示,
对象引用则是指针 PyObject * 。 PyObject 结构体定义于头文件 object.h
,路径为 Include/object.h ,代码如下:
1
2
3
4
5typedef struct _object {
_PyObject_HEAD_EXTRA //公共头部
Py_ssize_t ob_refcnt; //引用计数
struct _typeobject *ob_type; //类型指针
} PyObject;
引用计数 很好理解:对象被其他地方引用时加一,引用解除时减一; 当引用计数为零,便可将对象回收,这是最简单的垃圾回收机制。 类型指针 指向对象的 类型对象 ,类型对象 描述 实例对象 的数据及行为。
回过头来看 _PyObject_HEAD_EXTRA 宏的定义,同样在 Include/object.h
头文件内:
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 有定义,宏展开为两个指针,看名字是用来实现
双向链表 的:
1
2struct _object *_ob_next;
struct _object *_ob_prev;1
2
3
4typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;
于具体对象,视其大小是否固定,需要包含头部 PyObject 或 PyVarObject
。 为此,头文件准备了两个宏定义,方便其他对象使用:
|

而对于大小不固定的 列表对象 ,则需要在 PyVarObject 头部基础上, 用一个动态数组加以实现,数组存储列表包含的对象,即 PyObject 指针: |
---|
如图, PyListObject 底层
由一个数组实现,关键字段是以下 3 个: ob_item ,指向
动态数组 的指针,数组保存元素对象指针; allocated
,动态数组总长度,即列表当前的 容量 ; ob_size
,当前元素个数,即列表当前的 长度 ;
列表容量不足时,Python 会自动扩容,具体做法在讲解 list
源码时再详细介绍。 最后,介绍两个用于初始化对象头部的宏定义。
其中,PyObject_HEAD_INIT 一般用于 定长对象 ,将引用计数
ob_refcnt 设置为 1 并将对象类型 ob_type 设置成给定类型:
1
2
3#define PyObject_HEAD_INIT(type) \
{ _PyObject_EXTRA_INIT \
1, type },1
2#define PyVarObject_HEAD_INIT(type, size) \
{ PyObject_HEAD_INIT(type) size },
PyTypeObject,类型的基石#
在 PyObject 结构体,我们看到了 Python 中所有对象共有的信息。
对于内存中的任一个对象,不管是何类型,它刚开始几个字段肯定符合我们的预期:
引用计数 、 类型指针 以及变长对象特有的 元素个数 。
随着研究不断深入,我们发现有一些棘手的问题没法回答:
不同类型的对象所需内存空间不同,创建对象时从哪得知内存信息呢?
对于给定对象,怎么判断它支持什么操作呢? 对于我们初步解读过的
PyFloatObject 和 PyListObject ,并不包括这些信息。
事实上,这些作为对象的 元信息
,应该由一个独立实体保存,与对象所属 类型 密切相关。 注意到, PyObject
中包含一个指针 ob_type ,指向一个 类型对象 ,秘密就藏在这里。类型对象
PyTypeObject 也在 Include/object.h
中定义,字段较多,只讨论关键部分:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18typedef 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;
// ...
/* Attribute descriptor and subclassing stuff */
struct _typeobject *tp_base;
// ......
} PyTypeObject;
PyTypeObject 就是 类型对象 在 Python
中的表现形式,对应着面向对象中“类”的概念。
PyTypeObject 结构很复杂,但是我们不必在此刻完全弄懂它。
先有个大概的印象,知道 PyTypeObject 保存着对象的 元信息 ,描述对象的
类型 即可。 接下来,以 浮点 为例,考察 类型对象 和 实例对象
在内存中的形态和关系:
1
2
3
4
5
6
7>>> float
<class 'float'>
>>> pi = 3.14
>>> e = 2.71
>>> type(pi) is float
True
其中,两个浮点 实例对象 都是 PyFloatObject 结构体,
除了公共头部字段 ob_refcnt 和 ob_type ,专有字段
ob_fval 保存了对应的数值。 浮点 类型对象 是一个
PyTypeObject 结构体, 保存了类型名、内存分配信息以及浮点相关操作。
实例对象 ob_type 字段指向类型对象, Python 据此判断对象类型,
进而获悉关于对象的元信息,如操作方法等。 再次提一遍,float 、 pi 以及 e
等变量只是一个指向实际对象的指针。 由于浮点 类型对象
全局唯一,在 C 语言层面
作为一个全局变量静态定义即可,Python 的确就这么做。
浮点类型对象就藏身于 Object/floatobject.c 中, PyFloat_Type 是也:
1
2
3
4
5
6
7
8
9
10
11
12PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
0,
(destructor)float_dealloc, /* tp_dealloc */
// ...
(reprfunc)float_repr, /* tp_repr */
// ...
};
PyType_Type,类型的类型#
我们初步考察了 float 类型对象,知道它在 C 语言层面是 PyFloat_Type
全局静态变量。 类型是一种对象,它也有自己的类型,也就是 Python 中的 type
:
1
2>>> float.__class__
<class 'type'>1
2
3
4
5>>> class Foo(object):
... pass
...
>>> Foo.__class__
<class 'type'>1
2
3
4
5
6
7
8
9
10
11
12PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
(destructor)type_dealloc, /* tp_dealloc */
// ...
(reprfunc)type_repr, /* tp_repr */
// ...
};1
2
3
4
5>>> type.__class__
<class 'type'>
>>> type.__class__ is type
True
PyBaseObject_Type,类型之基#
object 是另一个特殊的类型,它是 所有类型的基类。
那么,怎么找到它背后的实体呢? 理论上,通过 PyFloat_Type 中 tp_base
字段顺藤摸瓜即可。 然而,我们发现这个字段在并没有初始化:
1
0, /* tp_base */
1
2if (PyType_Ready(&PyFloat_Type) < 0)
Py_FatalError("Can't initialize float type");1
2
3
4
5
6
7
8
9
10
11
12
13int
PyType_Ready(PyTypeObject *type)
{
// ...
base = type->tp_base;
if (base == NULL && type != &PyBaseObject_Type) {
base = type->tp_base = &PyBaseObject_Type;
Py_INCREF(base);
}
// ...
}
1 | PyTypeObject PyBaseObject_Type = { |
注意到, ob_type 字段指向 PyType_Type 跟 object 在 Python
中的行为时相吻合的:
1
2>>> object.__class__
<class 'type'>1
2>>> print(object.__base__)
None
创建一个对象时, 先创建一个 类型对象PyTypeObject(类型对象自始至终都是 只要一个的, 在C源码中, 就是定义了一个全局的变量), 保存要创建对象的类型信息, 接着再在 该类型对象的方法中创建指定的对象, 并将类型对象 PyTypeObject 置为该对象的属性之一, 如创建一个int对象, 先在PyInt_Type对象创建封装了信息之后再创建PyIntObject对象。
Python相比较于其他语言的好处是 其doc文档就在程序之中, 通过PyTypeObject结构体中的doc属性可以看到。