实例对象的生命周期
一. 泛型API和特型API
Python由C语言实现,对外提供C API目的是在C环境中实现交互。C API分为泛型API和特型API两类。
1.1 泛型API
泛型API 与类型无关,属于抽象对象层(Abstract Object Layer,AOL)。这类API参数是PyObject*
,可处理任意类型的对象,API 内部根据对象类型区别处理。
以打印对象的方法为例,
int
PyObject_Print(PyObject *op, FILE *fp, int flags)
方法第一个参数是任意类型的待打印对象,参数类型是PyObject*
。Python 底层一般都是通过PyObject*
引用达到泛型化的目的。
任意类型的对象,均可调用PyObject_Print
打印,
// 打印浮点对象
PyObject *fo = PyFloatObject_FromDouble(3.14);
PyObject_Print(fo, stdout, 0);
// 打印整数对象
PyObject *lo = PyFloatObject_FromLong(100);
PyObject_Print(lo, stdout, 0);
接口内部根据对象类型决定如何输出。
1.2 特型API
特型API与类型相关,属于具体对象层(Concrete Object Layer,COL)。这类API只能作用于某种类型的对象,Python内部为每一种内置对象提供了这样一组API。
如创建一个浮点数对象,
PyObject *
PyFloat_FromDouble(double fval)
二. 对象的创建
对象的元数据保存在对应的类型对象中,元数据中包含对象如何创建的信息。因此,实例对象由类型对象创建。
2.1 创建对象的方式
Python内部一般通过这两种方法创建对象,
- 通过C API,例如PyFloat_FromDouble在接口内部为PyFloatObject结构体分配内存,并初始化相关字段。多用于内建类型
- 通过类型对象,例如自定义Dog类需要分配多少内存,如何进行初始化,需要在类型对象中找。多用于自定义类型
2.2 创建对象的过程
2.1节中第二种通过类型对象创建实例对象的方法更为通用,同时支持内置类型和自定义类型。以通过浮点类型PyFloat_Type
创建浮点数对象为例,
>>> pi = float('3.14')
>>> pi
3.14
通过调用类型对象float
,实例化一个浮点实例pi
。在Python中,可以被调用的对象就是可调用对象。类型对象float
的类型对象是type
,当float
被调用时,执行的方法一定存在于type
的实体中,对应可以找到PyType_Type
中的tp_call
字段,该字段是一个函数指针。
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
// ...
(ternaryfunc)type_call, /* tp_call */
// ...
};
Python代码中,float(‘3.14’)在C层面等价于
PyFloat_Type.ob_type.tp_call(&PyFloat_Type, args, kwargs)
|
∨
PyType_Type.tp_call(&PyFloat_Type, args, kwargs)
args和kwargs参数先不介绍,详见函数机制部分内容。
type_call
方法,定义于Include/typeobject.c
,关键代码如下,
static 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);
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;
}
- 调用类型对象
tp_new
函数指针申请内存(第 7 行); - 必要时调用类型对象
tp_init
函数指针对对象进行初始化(第 15 行);
- 调用
float
,最终执行其类型对象type
的tp_call
方法 tp_call
方法调用float
的tp_new
函数为实例对象分配内存空间tp_call
函数必要时进一步调用tp_init
函数对实例对象进行初始化
三. 对象的多态性
3.1 Python中多态的应用
多态可以简单的理解为父类的引用指向子类对象。
Python创建一个对象,比如PyFloatObject,会分配内存并初始化。此后,Python内部统一通过一个PyObject*
变量来保存和维护这个对象,而不是通过PyFloatObject*
变量。这样可以实现更抽象的上层逻辑,而不用关心对象的实际类型和实现细节。
以计算对象哈希值为例,
Py_hash_t
PyObject_Hash(PyObject *v)
{
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
*/
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);
}
- 第4行先通过
ob_type
指针找到对象的类型 - 第6行通过类型对象的
tp_hash
函数指针,调用对应的哈希值计算函数
3.2 对象行为
不同对象的行为不同,比如哈希值计算方法就不同,由类型对象的tp_hash
字段决定。
除了tp_hash
,PyTypeObject结构体还定义了很多函数指针,这些指针最终都会指向某个函数,或者为空。 这些函数指针可以看做是类型对象中定义的操作,这些操作决定对应实例对象在运行时的行为。
Python 依据对象所属的类别,为每个类别都定义了一个标准操作集,每个操作集中包含了多个函数指针,
标准操作集被定义在PyTypeObject结构体中,
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 */
// ...
/* 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;
函数操作集 | 说明 |
---|---|
PyNumberMethods | 结构体定义了 数值型 操作 |
PySequenceMethods | 结构体定义了 序列型 操作 |
PyMappingMethods | 结构体定义了 关联型 操作 |
以类型对象float
为例,其对应的PyFloat_Type结构体及对应的函数操作集如下,
static 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 */
// ...
};
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
对象 不支持关联型操作
3.3 引用计数
垃圾回收机制的关键是对象的引用计数,每个Python对象都有一个ob_refcnt
字段,记录着对象当前的引用计数。 当对象被其他地方引用时,ob_refcnt
加一;当引用解除时,ob_refcnt
减一。 当ob_refcnt
为零,说明对象已经没有任何引用了,这时便可将其回收。通过sys模块的getrefcount
方法可以获取某个对象当前的引用计数。
Python定义了两个非常重要的宏,用于维护对象应用计数。
#define Py_INCREF(op) ( \
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \
((PyObject *)(op))->ob_refcnt++)
#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)
Py_INCREF
的第3行将对象应用计数加一Py_DECREF
的第5行将引用计数减一,并在第8行判断引用计数为0时回收对象
当一个对象引用计数为0, Python调用对象对应的析构函数销毁对象,但这并不意味着对象内存一定会回收。