深度解密 Python 的浮点数是怎么实现的?

2024年 5月 31日 79.5k 0

楔子

从现在开始,我们就来分析 Python 的内置对象,看看它们在底层是如何实现的。但说实话,我们在前面几篇文章中介绍对象的时候,已经说了不少了,不过从现在开始要进行更深入的分析。

除了对象本身,还要看对象支持的操作在底层是如何实现的。我们首先以浮点数为例,因为它是最简单的,没错,浮点数比整数要简单,至于为什么,等我们分析整数的时候就知道了。

浮点数的底层结构

要想搞懂浮点数的实现原理,就要知道它在底层是怎么定义的,当然在这之前我们已经见过它很多遍了。

// Include/cpython/floatobject.h
typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

它包含了一个公共头部 PyObject 和一个 double 类型的 ob_fval 字段,毫无疑问这个 ob_fval 字段负责存储浮点数的具体数值。

我们以 e = 2.71 为例,底层结构如下。

深度解密 Python 的浮点数是怎么实现的?-1图片

还是很简单的,每个对象在底层都是由结构体表示的,这些结构体中有的字段负责维护对象的元信息,有的字段负责维护具体的值。比如这里的  2.71,总要有一个字段来存储 2.71 这个值,而这个字段就是 ob_fval。所以浮点数的结构非常简单,直接使用一个 C 的 double 来维护。

假设我们要将两个浮点数相加,相信你已经知道解释器会如何做了?通过 PyFloat_AsDouble 将两个浮点数的 ob_fval 抽出来,然后相加,最后再根据相加的结果创建一个新的 PyFloatObject 即可。

浮点数是怎么创建的

下面来看看浮点数是如何创建的,在前面的文章中,我们说对象可以使用对应的特定类型 API 创建,也可以通过调用类型对象创建。

调用类型对象 float 创建实例对象,解释器会执行元类 type 的 tp_call,它指向了 type_call 函数。然后 type_call 内部会先调用类型对象(这里是 float)的 tp_new 为其实例对象申请一份空间,申请完毕之后对象就已经创建好了。然后再调用 tp_init,并将实例对象作为参数传递进去,进行初始化,也就是设置属性。

但是对于 float 来说,它内部的 tp_init 字段为 0,也就是空。

深度解密 Python 的浮点数是怎么实现的?-1图片

这就说明 float 没有 __init__,因为浮点数太过简单,只需要一个 tp_new 即可。我们举个例子:

class Girl1:

    def __init__(self, name, age):
        self.name = name
        self.age = age

# __new__  负责开辟空间、生成实例对象
# __init__ 负责给实例对象绑定属性

# 但其实 __init__ 所做的工作可以直接在 __new__ 当中完成
# 换言之有 __new__ 就足够了,其实可以没有 __init__
# 我们将上面的例子改写一下
class Girl2:

    def __new__(cls, name, age):
        instance = object.__new__(cls)
        instance.name = name
        instance.age = age
        return instance

g1 = Girl1("古明地觉", 16)
g2 = Girl2("古明地觉", 16)
print(g1.__dict__ == g2.__dict__)  # True

我们看到效果是等价的,因为 __init__ 负责给 self 绑定属性,而这个 self 是 __new__ 返回的。那么很明显,我们也可以在 __new__ 当中绑定属性,而不需要 __init__。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论