经过前面章节,我们知道 float 对象背后由 PyFloatObject 组织,对其结构也了然于胸。 那么,本节为何要重复讨论 float 对象呢?
一方面, 对象模型 中关于 float 对象的讨论, 着眼于 Python 面向对象体系的讲解,许多细节没来得及展开。 另一方面, float 作为 Python 中最简单的对象之一,“麻雀虽小,五脏俱全”, 拥有对象的全部必要属性。 以 float 对象为起点开启源码之旅,能够快速上手,为研究更复杂的内置对象打好基础,建立信心。
内部结构
float 实例对象在 Include/floatobject.h 中定义,结构很简单:
1
2
3
4
|
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
|
除了定长对象共用的头部,只有一个额外的字段 ob_fval ,存储对象所承载的浮点值。
float 类型对象又长啥样呢?
与实例对象不同, float 类型对象 全局唯一 ,因此可以作为 全局变量 定义。 在 C 文件 Objects/floatobject.c 中,我们找到了代表 float 类型对象的全局变量 PyFloat_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
|
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
0,
(destructor)float_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
(reprfunc)float_repr, /* tp_repr */
&float_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)float_hash, /* tp_hash */
0, /* tp_call */
(reprfunc)float_repr, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
float_new__doc__, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
float_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
float_methods, /* tp_methods */
0, /* tp_members */
float_getset, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
float_new, /* tp_new */
};
|
PyFloat_Type 中保存了很多关于浮点对象的 元信息 ,关键字段包括:
- tp_name 字段保存类型名称,常量 float ;
- tp_dealloc 、 tp_init 、 tp_alloc 和 tp_new 字段是对象创建销毁相关函数;
- tp_repr 字段是生成语法字符串表示形式的函数;
- tp_str 字段是生成普通字符串表示形式的函数;
- tp_as_number 字段是数值操作集;
- tp_hash 字段是哈希值生成函数;
PyFloat_Type 很重要,作为浮点 类型对象 ,它决定了浮点 实例对象 的 生死和行为 。 接下来,我们以不同小节分别展开讨论。
对象的创建
在 从创建到销毁,对象的生命周期 一节,我们初步了解到创建实例对象的一般过程。
调用类型对象 float 创建实例对象,Python 执行的是 type 类型对象中的 tp_call 函数。 tp_call 函数进而调用 float 类型对象的 tp_new 函数创建实例对象, 再调用 tp_init 函数对其进行初始化:
注意到, float 类型对象 PyFloat_Type 中 tp_init 函数指针为空。 这是因为 float 是一种很简单的对象,初始化操作只需要一个赋值语句,在 tp_new 中完成即可。
除了通用的流程, Python 为内置对象实现了对象创建 API ,简化调用,提高效率:
1
2
3
4
5
|
PyObject *
PyFloat_FromDouble(double fval);
PyObject *
PyFloat_FromString(PyObject *v);
|
- PyFloat_FromDouble 函数通过浮点值创建浮点对象;
- PyFloat_FromString 函数通过字符串对象创建浮点对象;
以 PyFloat_FromDouble 为例,特化的对象创建流程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
PyObject *
PyFloat_FromDouble(double fval)
{
PyFloatObject *op = free_list;
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op);
numfree--;
} else {
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
if (!op)
return PyErr_NoMemory();
}
/* Inline PyObject_New */
(void)PyObject_INIT(op, &PyFloat_Type);
op->ob_fval = fval;
return (PyObject *) op;
}
|
- 为对象 分配内存空间 ,优先使用 空闲对象缓存池 (第 4-12 行);
- 初始化 对象类型 字段 ob_type 以及 引用计数 字段 ob_refcnt (第 14 行);
- 将 ob_fval 字段初始化为指定 浮点值 (第 15 行);
其中,宏 PyObject_INIT 在头文件 Include/objimpl.h 中定义:
1
2
|
#define PyObject_INIT(op, typeobj) \
( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )
|
宏 _Py_NewReference
将对象引用计数初始化为 1 ,在 Include/Object.h 中定义:
1
2
3
4
|
#define _Py_NewReference(op) ( \
_Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA \
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \
Py_REFCNT(op) = 1)
|
至此,我们已经解锁了 float 对象的内部结构和创建过程。那么,float 对象是如何进行销毁的?前面提到的 空闲对象缓存池 又是怎么一回事呢?点击 阅读原文,获取更多细节!
【Python源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注: