长亭百川云 - 文章详情

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

古明地觉的编程教室

42

2024-07-13

楔子

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

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

浮点数的底层结构

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

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

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

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

还是很简单的,每个对象在底层都是由结构体表示的,这些结构体中有的字段负责维护对象的元信息,有的字段负责维护具体的值。比如这里的  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,也就是空。

这就说明 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__。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2