C API#
开始讨论对象创建前,先介绍 Python 提供的 C API 。 Python 是用 C 写成的,对外提供了 C API ,让用户可以从 C 环境中与其交互。 Python 内部也大量使用这些 API ,为了更好研读源码,先系统了解 API 组成结构很有必要。 C API 分为两类: 泛型 API 以及 特型 API。
泛型 API#
泛型 API 与类型无关,属于 抽象对象层 ( Abstract Object Layer ),简称
AOL 。 这类 API 参数是 PyObject*
,可处理任意类型的对象, API 内部根据对象类型区别处理。
以对象打印函数为例:
1
int PyObject_Print(PyObject *op, FILE *fp, int flags)
接口第一个参数为待打印对象,可以是任意类型的对象,因此参数类型是
PyObject* 。 Python 内部一般都是通过 PyObject*
引用对象,以达到泛型化的目的。 对于任意类型的对象,均可调用
PyObject_Print 将其打印出来:
1
2
3
4
5
6
7
8// 打印浮点对象
PyObject *fo = PyFloatObject_FromDouble(3.14);
PyObject_Print(fo, stdout, 0);
// 打印整数对象
PyObject *lo = PyFloatObject_FromLong(100);
PyObject_Print(lo, stdout, 0);
PyObject_Print 接口内部根据对象类型,决定如何输出对象。
特型 API#
特型 API 与类型相关,属于 具体对象层 ( Concrete Object Layer ),简称
COL 。 这类 API 只能作用于某种类型的对象,例如浮点对象 PyFloatObject 。
Python 内部为每一种内置对象提供了这样一组 API ,举例如下:
1
PyObject * PyFloat_FromDouble(double fval)
对象的创建#
经过前面的理论学习,我们知道对象的 元数据
保存在对应的 类型对象 中,元数据当然也包括
对象如何创建 的信息。 因此,有理由相信 实例对象
由 类型对象 创建。 不管创建对象的流程如何,最终的关键步骤都是
分配内存 。 Python 对 内建对象
是无所不知的,因此可以提供 C API ,直接分配内存并执行初始化。 以
PyFloat_FromDouble 为例,在接口内部为 PyFloatObject
结构体分配内存,并初始化相关字段即可。 对于用户自定义的类型 class
Dog(object) , Python 就无法事先提供 PyDog_New 这样的 C API 了。
这种情况下,就只能通过 Dog 所对应的类型对象创建实例对象了。
至于需要分配多少内存,如何进行初始化,答案就需要在
类型对象 中找了。 总结起来,Python
内部一般通过这两种方法创建对象: - 通过 C API ,例如
PyFloat_FromDouble ,多用于内建类型; -
通过类型对象,例如 Dog ,多用于自定义类型;
通过类型对象创建实例对象,是一个更通用的流程,同时支持内置类型和自定义类型。
以创建浮点对象为例,我们还可以通过浮点类型 PyFloat_Type 来创建:
1
2
3>>> pi = float('3.14')
>>> pi
3.14
问题来了,可调用对象被调用时,执行什么函数呢?
由于类型对象保存着实例对象的元信息, float 类型对象的类型是 type
,因此秘密应该就隐藏在 type 中。 再次考察 PyType_Type ,我们找到了
tp_call 字段,这是一个 函数指针:
1
2
3
4
5
6
7
8
9
10
11PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
// ...
(ternaryfunc)type_call, /* tp_call */
// ...
};1
PyFloat_Type.ob_type.tp_call(&PyFloat_Type, args, kwargs)
1
PyType_Type.tp_call(&PyFloat_Type, args, kwargs)
1
type_call(&PyFloat_Type, args, kwargs)
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
26static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *obj;
// ...
obj = type->tp_new(type, args, kwds); //为对象分配内存
obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
if (obj == NULL)
return NULL;
// ...
type = Py_TYPE(obj); //获取PyType_Type
if (type->tp_init != NULL) {
int res = type->tp_init(obj, args, kwds); //初始化对象
if (res < 0) {
assert(PyErr_Occurred());
Py_DECREF(obj); //引用减一
obj = NULL;
}
else {
assert(!PyErr_Occurred());
}
}
return obj;
}
可以看到,关键的步骤有两个: 1. 调用类型对象 tp_new 函数指针 申请内存 (第 7 行); 2. 必要时调用类型对象 tp_init 函数指针对对象进行 初始化 (第 15 行); 至此,对象的创建过程已经非常清晰了:

总结一下,float 类型对象是 可调用对象 ,调用 float 即可创建实例对象: 1. 调用 float , Python 最终执行其类型对象 type 的 tp_call 函数; 2. tp_call 函数调用 float 的 tp_new 函数为实例对象分配 内存空间 ; 3. tp_call 函数必要时进一步调用 tp_init 函数对实例对象进行 初始化 ;
对象的多态性#
Python 创建一个对象,比如 PyFloatObject ,会分配内存,并进行初始化。
此后, Python 内部 统一通过一个 PyObject*
变量来保存和维护这个对象,而不是通过 PyFloatObject* 变量。 通过
PyObject* 变量保存和维护对象,可以
实现更抽象的上层逻辑,而不用关心对象的实际类型和实现细节。
以对象哈希值计算为例,假设有这样一个函数接口:
1
Py_hash_t PyObject_Hash(PyObject *v);
1
2PyObject *fo = PyFloatObject_FromDouble(3.14);
PyObject_Hash(fo);1
2PyObject *lo = PyLongObject_FromLong(100);
PyObject_Hash(lo);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23Py_hash_t
PyObject_Hash(PyObject *v)
{
//类似强制类型转换,但是不像c++一样,用父类指针调用子类就行
//这里的结构体里面指针有ob_type和tp_hash,标记了类型和哈希函数
PyTypeObject *tp = Py_TYPE(v);
if (tp->tp_hash != NULL)
return (*tp->tp_hash)(v);
/* To keep to the general practice that inheriting
* solely from object in C code should work without
* an explicit call to PyType_Ready, we implicitly call
* PyType_Ready here and then check the tp_hash slot again
* 隐式调用PyType_Ready,然后再次检查tp_hash插槽
*/
if (tp->tp_dict == NULL) {
if (PyType_Ready(tp) < 0)
return -1;
if (tp->tp_hash != NULL)
return (*tp->tp_hash)(v);
}
/* Otherwise, the object can't be hashed */
return PyObject_HashNotImplemented(v);
}
对象的行为#
不同对象的行为不同,比如哈希值计算方法就不同,由类型对象中 tp_hash
字段决定。 除了 tp_hash ,我们看到 PyTypeObject
结构体还定义了很多函数指针,这些指针最终都会指向某个函数,或者为空。
这些函数指针可以看做是 类型对象(PyTypeObject)
中定义的 操作 ,这些操作决定对应 实例对象 在运行时的
行为 。 尽管如此,不同对象也有一些共性。 举个例子,整数对象 和
浮点对象 都支持加减乘除等 数值型操作 :
1
2
3
4
5>>> 1 + 2
3
>>> 3.14 * 3.14
9.85961
2
3
4
5
6
7>>> t = ('apple', 'banana', 'car', 'dog')
>>> t[-1]
'dog'
>>> l = ['alpha', 'beta']
>>> l[-1]
'beta'

Python 便以此为依据,为每个类别都定义了一个 标准操作集 : - PyNumberMethods 结构体定义了 数值型 操作; - PySequenceMethods 结构体定义了 序列型 操作; - PyMappingMethods 结构体定义了 关联型 操作;
只要 类型对象 提供相关 操作集 , 实例对象 便具备对应的 行为 。
操作集字段如下:
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 */
// ...
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
// ...
/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;
// ...
} PyTypeObject;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24static PyNumberMethods float_as_number = {
float_add, /* nb_add */
float_sub, /* nb_subtract */
float_mul, /* nb_multiply */
float_rem, /* nb_remainder */
float_divmod, /* nb_divmod */
float_pow, /* nb_power */
// ...
};
//PyFloat_Type是PyTypeObject结构体类型,只是重写了
//注意与c++父类区分开,这里只是重定义了一些具体参数
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
// ...
&float_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
// ...
};
- 字段 tp_as_number 非空,因此 float 对象 支持数值型操作 ;
- 字段 tp_as_sequence 为空,因此 float 对象 不支持序列型操作 ;
- 字段 tp_as_mapping 为空,因此 float 对象 不支持关联型操作 ; 注意到, float_as_number 变量中相关函数指针都初始化为对应的 float 版本操作函数。
引用计数#
C/C++
赋予程序员极大的自由,可以任意申请内存,并按自己的意图灵活管理。
然而,权利的另一面则对应着 责任 ,一旦内存不再使用,程序员必须将其释放。
这给程序员带来极大的 工作负担 ,并导致大量问题: 内存泄露 、
野指针 、 越界访问 等。 许多后来兴起的开发语言,如 Java 、
Golang 等,选择 由语言本身负责内存的管理 。 垃圾回收机制
的引入,程序员摆脱了内存管理的噩梦,可以更专注于业务逻辑。
于此同时,开发人员失去了灵活使用内存的机会,也牺牲了一定的执行效率。
随着垃圾回收机制日益完善,可在大部分对性能要求不苛刻的场景中引入,利大于弊。
Python 也采用垃圾回收机制,代替程序员进行繁重的内存管理,提升开发效率
的同时,降低 bug 发生的几率。 Python 垃圾回收机制的关键是对象的
引用计数 ,它决定了一个对象的生死。 我们知道每个 Python
对象都有一个 ob_refcnt 字段,记录着对象当前的引用计数。
当对象被其他地方引用时, ob_refcnt 加一; 当引用解除时, ob_refcnt
减一。 当 ob_refcnt 为零,说明对象已经没有任何引用了,这时便可将其回收。
Python 对象创建后,引用计数设为 1 :
1
2
3>>> a = 3.14
>>> sys.getrefcount(a)
21
2
3>>> b = a
>>> sys.getrefcount(a)
31
2
3
4
5>>> l = [a]
>>> l
[3.14]
>>> sys.getrefcount(a)
41
2
3>>> del b
>>> sys.getrefcount(a)
31
2
3>>> l.clear()
>>> sys.getrefcount(a)
21
>>> del a
为此, Python 定义了两个非常重要的宏,用于维护对象应用计数。 其中,
Py_INCREF 将对象应用计数加一 ( 3 行):
1
2
3#define Py_INCREF(op) ( \
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \
((PyObject *)(op))->ob_refcnt++)1
2
3
4
5
6
7
8
9
10#define Py_DECREF(op) \
do { \
PyObject *_py_decref_tmp = (PyObject *)(op); \
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \
--(_py_decref_tmp)->ob_refcnt != 0) \
_Py_CHECK_REFCNT(_py_decref_tmp) \
else
\\ 调用对象对应的析构函数销毁对象
_Py_Dealloc(_py_decref_tmp); \
} while (0)