内部结构#
float 实例对象在 Include/floatobject.h 中定义如下:
1
2
3
4typedef struct {
PyObject_HEAD //定长对象共用的头部
double ob_fval; //额外字段,存储对象所承载的浮点值
} PyFloatObject;

float 类型对象#
与实例对象不同, float 类型对象 全局唯一 ,因此可以作为 全局变量 定义。 在 C 文件 Objects/floatobject.c 中,我们找到了代表 float 类型对象的全局变量 PyFloat_Type :
1 | PyTypeObject PyFloat_Type = { |
PyFloat_Type 中保存了很多关于浮点对象的 元信息
PyFloat_Type 很重要,作为浮点 类型对象 ,它决定了浮点 实例对象 的 生死和行为 。
对象的创建#
调用类型对象 float 创建实例对象: Python 执行的是 type 类型对象中的 tp_call 函数。 tp_call 函数进而调用 float 类型对象的 tp_new 函数创建实例对象, 再调用 tp_init 函数对其进行初始化:

除了通用的流程, Python 为内置对象实现了对象创建 API
,简化调用,提高效率:
1
2
3
4
5PyObject *
PyFloat_FromDouble(double fval); /*通过浮点值创建浮点对象*/
PyObject *
PyFloat_FromString(PyObject *v); /*通过字符串对象创建浮点对象*/
以 PyFloat_FromDouble 为例,特化的对象创建流程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22PyObject *
PyFloat_FromDouble(double fval)
{
PyFloatObject *op = free_list;
/*为对象 分配内存空间,优先使用空闲对象缓存池 */
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op);
numfree--;
}
/*空闲对象缓存池为空,调用PyObject_MALLOC申请内存 */
else {
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
if (!op)
return PyErr_NoMemory();
}
/* Inline PyObject_New */
(void)PyObject_INIT(op, &PyFloat_Type);
/*将 ob_fval 字段初始化为指定 浮点值 */
op->ob_fval = fval;
return (PyObject *) op;
}
上面用到的宏 PyObject_INIT 在头文件 Include/objimpl.h 中定义为:
1
2#define PyObject_INIT(op, typeobj) \
( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) ) //前半部分调用 Py_TYPE(op) 初始化 对象类型 字段 ob_type,后面语句初始化 引用计数 字段 ob_refcnt
上面提到的宏定义 Py_TYPE,位于 Include/object.h
头文件:
1
#define Py_TYPE(ob) (((PyObject*)(ob))&ob_type)
宏 _Py_NewReference,在 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) /*将对象引用计数初始化为 1*/
# 对象的销毁 当对象不再需要时, Python 通过 Py_DECREF 或者
Py_XDECREF 宏减少引用计数; 当引用计数降为 0 时, Python 通过
**_Py_Dealloc** 宏回收对象:
|
空闲对象缓存池#
浮点运算背后涉及 大量临时对象创建以及销毁 ,以下面计算为例:
1
>>> area = pi * r ** 2
创建对象时需要分配内存,销毁对象时又需要回收内存。 大量临时对象创建销毁 ,意味着 大量内存分配回收操作 ,这显然是是不可接受的。
因此 Python 在浮点对象销毁后,并不急于回收内存,而是将对象放入一个 空闲链表 。 后续需要创建浮点对象时,先到空闲链表中取,省去分配内存的开销。
浮点对象的空闲链表同样在 Objects/floatobject.c 中定义:1 | #ifndef PyFloat_MAXFREELIST |
因此创建浮点对象时,可以从 链表中取出空闲对象,省去
申请内存的开销! 以 PyFloat_FromDouble 为例:
1
2
3
4
5
6
7
8PyFloatObject *op = free_list; /*op指向第一个 空闲对象*/
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op); /*free_list指向第一个 空闲对象(op)的ob_type所指向的 下一个空闲对象(相当于链表的头部删除)*/
numfree--; /*更新空闲链表维护的数量*/
} else {
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject)); /*free_list为空时,重新分配内存*/
// ...
}
对象销毁时, Python 将其缓存在空闲链表中,以备后用。考察
float_dealloc 函数:
1
2
3
4
5
6
7if (numfree >= PyFloat_MAXFREELIST) { /*销毁时,判断free_list是否达到最大容量*/
PyObject_FREE(op); /*回收对象内*/
return;
}
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list; /*op的ob_type指向当前 第一个空闲对象*/
free_list = op; /* free_list指向op(相当于链表的头部插入)*/
对象的行为#
PyFloat_Type 中定义了很多函数指针,包括 tp_repr 、 tp_str 、 tp_hash
等。 这些函数指针将一起决定 float 对象的行为,例如 tp_hash
函数决定浮点哈希值的计算:
1
2
3>>> pi = 3.14
>>> hash(pi)
3228180212899174431
2
3
4
5static Py_hash_t
float_hash(PyFloatObject *v)
{
return _Py_HashDouble(v->ob_fval);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21typedef struct {
/* Number implementations must check *both*
arguments for proper type and implement the necessary conversions
in the slot functions themselves. */
binaryfunc nb_add;
binaryfunc nb_subtract;
binaryfunc nb_multiply;
binaryfunc nb_remainder;
binaryfunc nb_divmod;
ternaryfunc nb_power;
unaryfunc nb_negative;
// ...
binaryfunc nb_inplace_add;
binaryfunc nb_inplace_subtract;
binaryfunc nb_inplace_multiply;
binaryfunc nb_inplace_remainder;
ternaryfunc nb_inplace_power;
//...
} PyNumberMethods;
- 一元函数 ( unaryfunc ): 需要传入一个参数的函数。
- 二元函数 ( binaryfunc): 需要传入两个参数的函数。
回到 Objects/floatobject.c 观察浮点对象数值操作集 float_as_number
是如何初始化的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17static 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 */
(unaryfunc)float_neg, /* nb_negative */
// ...
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
0, /* nb_inplace_multiply */
0, /* nb_inplace_remainder */
0, /* nb_inplace_power */
// ...
};1
2
3
4>>> a = 1.5
>>> b = 1.1
>>> a + b
2.61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static PyObject *
float_add(PyObject *v, PyObject *w)
{
double a,b;
//将两个参数对象转化成浮点值,CONVERT_TO_DOUBLE是一个宏,将PyFloatObject里面的ob_fval抽出来给double变量
CONVERT_TO_DOUBLE(v, a);
CONVERT_TO_DOUBLE(w, b);
PyFPE_START_PROTECT("add", return 0)
//对两个浮点值求和
a = a + b;
PyFPE_END_PROTECT(a)
//创建一个新浮点对象保存计算结果并返回
return PyFloat_FromDouble(a);
}
所以如果是 C中的两个浮点数相加,直接a + b就可以了,编译之后就是一条简单的机器指令,然而 Python则需要额外做很多其它工作。从一个简单的加法上面就可以看出来Python为什么会比C慢几十倍了。