深度解密 Python 的浮點數(shù)是怎么實現(xiàn)的?
楔子
從現(xiàn)在開始,我們就來分析 Python 的內(nèi)置對象,看看它們在底層是如何實現(xiàn)的。但說實話,我們在前面幾篇文章中介紹對象的時候,已經(jīng)說了不少了,不過從現(xiàn)在開始要進行更深入的分析。
除了對象本身,還要看對象支持的操作在底層是如何實現(xiàn)的。我們首先以浮點數(shù)為例,因為它是最簡單的,沒錯,浮點數(shù)比整數(shù)要簡單,至于為什么,等我們分析整數(shù)的時候就知道了。
浮點數(shù)的底層結(jié)構(gòu)
要想搞懂浮點數(shù)的實現(xiàn)原理,就要知道它在底層是怎么定義的,當然在這之前我們已經(jīng)見過它很多遍了。
// Include/cpython/floatobject.h
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
它包含了一個公共頭部 PyObject 和一個 double 類型的 ob_fval 字段,毫無疑問這個 ob_fval 字段負責存儲浮點數(shù)的具體數(shù)值。
我們以 e = 2.71 為例,底層結(jié)構(gòu)如下。
圖片
還是很簡單的,每個對象在底層都是由結(jié)構(gòu)體表示的,這些結(jié)構(gòu)體中有的字段負責維護對象的元信息,有的字段負責維護具體的值。比如這里的 2.71,總要有一個字段來存儲 2.71 這個值,而這個字段就是 ob_fval。所以浮點數(shù)的結(jié)構(gòu)非常簡單,直接使用一個 C 的 double 來維護。
假設(shè)我們要將兩個浮點數(shù)相加,相信你已經(jīng)知道解釋器會如何做了?通過 PyFloat_AsDouble 將兩個浮點數(shù)的 ob_fval 抽出來,然后相加,最后再根據(jù)相加的結(jié)果創(chuàng)建一個新的 PyFloatObject 即可。
浮點數(shù)是怎么創(chuàng)建的
下面來看看浮點數(shù)是如何創(chuàng)建的,在前面的文章中,我們說對象可以使用對應(yīng)的特定類型 API 創(chuàng)建,也可以通過調(diào)用類型對象創(chuàng)建。
調(diào)用類型對象 float 創(chuàng)建實例對象,解釋器會執(zhí)行元類 type 的 tp_call,它指向了 type_call 函數(shù)。然后 type_call 內(nèi)部會先調(diào)用類型對象(這里是 float)的 tp_new 為其實例對象申請一份空間,申請完畢之后對象就已經(jīng)創(chuàng)建好了。然后再調(diào)用 tp_init,并將實例對象作為參數(shù)傳遞進去,進行初始化,也就是設(shè)置屬性。
但是對于 float 來說,它內(nèi)部的 tp_init 字段為 0,也就是空。
圖片
這就說明 float 沒有 __init__,因為浮點數(shù)太過簡單,只需要一個 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__。