楔子
从现在开始,我们就来分析 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__。