内建类型—float
一. 内部结构
1.1 实例对象结构
float
类型的定义写在 include/floatobject.h 文件中,类型定义十分简单,
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
float实例对象由一个定长头部和一个double类型字段ob_fval
构成。示意图如下,
1.2 类型对象结构
类型对象全局唯一,作为全局变量定义在Objects/floatobject.c中的PyFloat_Type
。
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 */
};
类型对象中保存float对象很多关键的元信息,这些信息决定float实例对象的创建、销毁和行为。
字段名 | 解释 |
---|---|
tp_name | 保存类型名称,值为float |
tp_dealloc、tp_init、tp_alloc和tp_new | 对象创建销毁相关函数,float_dealloc/0/0/float_new |
tp_repr | 生成语法字符串表示形式的函数,float_repr |
tp_str | 字段是生成普通字符串表示形式的函数,float_repr |
tp_as_number | 字段是数值操作集,对应代码float_as_number |
tp_hash | 字段是哈希值生成函数,对应float_hash |
二. 创建和销毁
通过调用类型对象中的方法实现实例对象的创建和销毁。
2.1 对象的创建
Python执行type类型对象中的tp_call
函数。tp_call
函数进而调用float类型对象的 tp_new
函数创建实例对象,再调用tp_init
函数对其进行初始化。
float类型对象的tp_init
函数指针为空。因为float是很简单的对象,初始化赋值语句,在 tp_new
中完成即可。两个函数根据输入值类型的不同选择调用下面两个创建对象API,
PyObject * PyFloat_FromDouble(double fval); // 通过浮点值创建浮点对象
PyObject * PyFloat_FromString(PyObject *v); // 通过字符串创建浮点对象
以double创建浮点数为例,
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();
}
// 初始化对象类型字段ob_type以及引用计数字段 ob_refcnt
(void)PyObject_INIT(op, &PyFloat_Type);
// ob_fval字段初始化为指定值
op->ob_fval = fval;
return (PyObject *) op;
}
上方代码中,宏PyObject_INIT
在include/objimpl.h 中定义,
#define PyObject_INIT(op, typeobj) \
( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )
// 宏_Py_NewReference将引用计数初始化为1,在include/Object.h中定义
#define _Py_NewReference(op) ( \
_Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA \
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \
Py_REFCNT(op) = 1)
2.2 对象的销毁
Python 通过Py_DECREF
或者Py_XDECREF
宏减少引用计数,当计数降为0时,通过_Py_Dealloc
宏回收对象。
对象销毁调用的是类型对象的float_dealloc
函数,
static void
float_dealloc(PyFloatObject *op)
{
if (PyFloat_CheckExact(op)) {
if (numfree >= PyFloat_MAXFREELIST) {
PyObject_FREE(op);
return;
}
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list;
free_list = op;
}
else
Py_TYPE(op)->tp_free((PyObject *)op);
}
float_dealloc
函数在_Py_Dealloc
宏中调用,
#define _Py_Dealloc(op) ( \
_Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA \
(*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))
2.3 创建与销毁过程图示
三. 空闲对象池
3.1 空闲对象池概念
浮点运算背后涉及大量临时对象创建以及销毁,如
area = pi * r ** 2
- 计算 r 2 r^2 r2的结果由一个临时对象来保存,假设是t
- 计算pi与t的乘积结果赋值给变量area
- 销毁临时对象t
大量临时对象创建销毁,意味着大量内存分配回收操作。Python在浮点对象销毁后,并不急于回收内存,而是将对象放入一个空闲链表。后续需要创建浮点对象时,先到空闲链表中取,省去分配内存的开销。
浮点对象的空闲链表在Objects/floatobject.c中定义,
#ifndef PyFloat_MAXFREELIST
#define PyFloat_MAXFREELIST 100
#endif
static int numfree = 0;
static PyFloatObject *free_list = NULL;
变量名 | 解释 |
---|---|
free_list | 指向空闲链表头节点的指针 |
numfree | 维护空闲链表当前长度 |
PyFloat_MAXFREELIST 宏 | 限制空闲链表的最大长度,避免占用过多内存 |
为了保持简洁,Python把ob_type
字段当作next指针来用,将空闲对象串成链表。
3.2 创建过程的使用
在创建float对象的过程中,2.1节PyFloat_FromDouble
方法里的调用过程,
PyFloatObject *op = free_list;
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op);
numfree--;
} else {
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
// ...
}
- 检查
free_list
是否为空(第 2 行) - 如果
free_list
非空,取出头节点备用,并将numfree
减一(第 3-4 行) - 如果
free_list
为空,则调用PyObject_MALLOC
分配内存(第 6 行)
3.3 销毁过程的使用
对象销毁时,Python将其缓存在空闲链表中,以备后用。考察float_dealloc
函数,
if (numfree >= PyFloat_MAXFREELIST) {
PyObject_FREE(op);
return;
}
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list;
free_list = op;
- 第1-4行,空闲链表长度达到限制值,调用
PyObject_FREE
回收对象内存 - 第5-7行,空闲链表长度暂未达到限制,将对象插到空闲链表头部
空闲对象缓存池在提高对象分配效率方面发挥着至关重要的作用,在其他内置对象中会经常看到它。
四. 对象行为
PyFloat_Type中定义了很多函数指针,包括tp_repr
、tp_str
、tp_hash
等。 它们一起决定float对象的行为。
Python将常见的数值操作抽象成数值操作集PyNumberMethods
。在头文件include/object.h中定义。函数根据参数个数可以分为:一元函数(unaryfunc)、二元函数(binaryfunc)等。
typedef struct {
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;
Objects/floatobject.c中浮点对象数值操作集float_as_number
的初始化如下,
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 */
(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 */
// ...
};
其中,以float_add
为例,该二元函数定义于Objects/floatobject.h中,
static PyObject * float_add(PyObject *v, PyObject *w)
{
double a,b;
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);
}
- 将两个参数对象转化成浮点值( 5-6 行)
- 对两个浮点值求和( 8 行)
- 创建一个新浮点对象保存计算结果并返回