python 物件池

AtomG發表於2019-10-09

一個奇怪的特性

這個學期學校又重新開始了 python 的學習,我大一的時候就自學過 python 所以就在自己看書複習快忘光的知識,在看書過程中發現了一個奇怪的特性,在互動式程式設計框中輸入:

a=1
b=1
a is b # true
# is 判斷兩個常量是否指向同一個物件複製程式碼

書中說 CPython 建立一個 int 型物件,並使 a b 兩常量都指向同一個 int 型物件,難道 python 中的物件都是複用的嗎,這可是和其他語言完全不同的特性。可是書中接下來又甩出一段程式碼:

a=10.1
b=10.1
a is b  #false複製程式碼

這樣建立的 a,b 又是指向了兩個不同的 float 型物件,難道這種規則還和資料型別有關係嗎,接下來我又嘗試瞭如下幾種資料格式:

image

從圖片中可以看到,int、str 複用了相同的物件,而list、float 並沒有,物件的複用規則果然和資料型別有關係,是什麼機制導致了這種現象呢,在查詢資料的過程中我接觸了到這樣一個名詞 「物件池

物件池

整數

簡單來說 python 中存在著一個「小整數物件池」,所有 [-5,256] 區間內的小整數都在這裡面,當有小整數物件建立的時候,會先到這個池中看看有沒有,如果存在的話會直接指向這個已經存在的物件,所以就會發生上述程式碼中 a,b 指向同一個 1 物件的現象。

字串

在上述程式碼截圖中可以看到,字串物件和整數物件存在著一樣的行為,那麼是不是也有一個 「字串物件池」呢,非也。
關於字串,python直譯器的處理機制並不一樣,其使用的是 intern(字串滯留) 機制,簡單說就是維護一個字典,這個字典維護已經建立字串(key)和它的字串物件的地址(value),每次建立字串物件都會和這個字典比較,沒有就建立,重複了就用指標進行引用就可以了。而又只有長度小於 20 的字串會呼叫這個機制,實驗截圖:

image

編譯單元

既然討論到了常量物件的儲存機制,就得涉及到「編譯單元

背景知識

CPython的程式碼的“編譯單元”是函式——每個函式單獨編譯,得到的結果是一個PyFunctionObject物件,其中帶有位元組碼、常量池等各種資訊。Python的頂層程式碼也被看作一個函式。

編譯單元的區別

關於互動式直譯器和 python原始碼檔案解釋單元的區別引用知乎使用者 @RednaxelaFX 的回答

在CPython的互動式直譯器中,每輸入一行可以立即執行的程式碼,Python就會把它當作一個編譯單元來編譯到位元組碼並解釋執行;如果輸入的程式碼尚未構成一個完整的單元,例如函式宣告或者類宣告,則等到獲得了完整單元的輸入後再當作一個編譯單元來處理。

當使用python命令去整體解釋一個Python原始碼檔案時,編譯單元就是函式,Python的頂層程式碼也被看作一個函式。

編譯單元的常量池

每個編譯單元都有自己獨有的常量池,在同一個編譯單元(PyFunctionObject)裡出現的值相同的常量,只會在常量池裡出現一份,一定會對應到執行時的同一個物件。

而在不同的編譯單元中,指向問題就交由給上邊兩種機制進行處理,小整數池和 intern 機制使得不同編譯單元的常量也可能指向同一個物件。

這樣一來在一份單獨 .py 檔案中執行下列程式碼就會出現看似和上文矛盾的地方

# Test.py
a=10.1
b=10.1
print(a is b) # true複製程式碼

其實這就是因為 a b 都處於頂層程式碼,屬於同一個編譯單元,所以共享同一個常量池,而上文的程式碼是在互動式直譯器中執行的,兩個賦值語句處於兩個兩行是不同的編譯單元,所以不能共享。

所以:

>>>a=10.1;b=10.1;
>>>a is b
True複製程式碼

賦值語句處於同一行,是在同一個編譯單元中,指向同一個物件。

總結

至此,關於常量物件儲存機制的問題就全部搞定了,在同一編譯單元中,共享常量池,不同編譯單元中使用小整數池和 intern 機制。感覺還挺複雜,不過這是作為學習來進行分析,在實際開發中根據根據直覺來使用也不會有什麼問題。值得一提的是,此文所說的共享物件全部是指的不可變物件,比如字串,整數,小數,而列表( list ),字典(dist)等可變物件是不存在共享物件一說,每次新建一個列表都必定會在記憶體中新建一個列表物件。

Home

Welcome to my Blog

相關文章