小试牛刀,解剖浮点对象 float

经过前面章节,我们知道 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;
}
  1. 为对象 分配内存空间 ,优先使用 空闲对象缓存池 (第 4-12 行);
  2. 初始化 对象类型 字段 ob_type 以及 引用计数 字段 ob_refcnt (第 14 行);
  3. 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源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注:

【Python源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注: