1. 程式人生 > 程式設計 >Python小整數物件池和字串intern例項解析

Python小整數物件池和字串intern例項解析

is用於判斷兩個物件是否為同一個物件,具體來說是兩個物件在記憶體中的位置是否相同。

python為了提高效率,節省記憶體,在實現上大量使用了緩衝池技術和字串intern技術。

整數和字串是不可變物件,也就意味著可以用來共享,如100個“python”字串變數可以共享一個“python”字串物件,而不是建立100個“python”字串。

小整數物件池

為了應對小整數的頻繁使用,python使用對小整數進行了快取,預設範圍為[-5,256],在這個範圍內的所有整數被python完全地快取,當有變數使用這些小整數時,增加對應小整數物件的引用即可。

>>> i = -5
>>> j = -5
>>> i is j # i和j是同一個物件
True
>>> i = 256
>>> j = 256
>>> i is j # i和j是同一個物件
True
>>> i = 257
>>> j = 257
>>> i is j # i和j是不同物件
False

由上面的例項可以看到,當變數在[-5,256]之間時,兩個值相同的變數事實上會引用到同一個小整數物件上,也就是小整數物件池中的物件,而不會去建立兩個物件。而當變數超出了這個範圍,兩個值相同的變數也會各自建立整數物件,所以兩者對應的物件不同。

字串intern

如果當前變數引用的字串物件已經存在的話,直接增加對應字串物件的引用,而不去建立新的字串物件,這就是字串intern機制。

>>> i = "12"
>>> j = "12"
>>> i is j
True

在詳細探討字串intern機制之前,先看一個奇怪的問題:

>>> i = "1 2"

>>> j = "1 2"
>>> i is j
False

i = "1 2"
j = "1 2"
print(i is j)

輸出結果

True

上述程式碼分開執行,結果為False,但是合在一起結果卻為True,也就是說分開執行的時候,i,j指向不同物件,而合在一起的時候i,j卻指向了相同物件。為了明白其中的緣由,需要簡單理解python的編譯機制。

編譯機制

在python中,萬物皆物件,包括程式碼本身也是一種物件。python用code物件表示程式碼,程式碼編譯後產生code物件。通常一個作用域對應一個code物件。

i = "1 2"
j = "1 2"
print(i is j)

def f():
  pass

編譯結果

2 0 LOAD_CONST 0 ('1 2')
2 STORE_NAME 0 (i)

3 4 LOAD_CONST 0 ('1 2')
6 STORE_NAME 1 (j)

5 8 LOAD_CONST 1 (<code object f at 0x00000200F257CF60,file "small_int.py",line 5>)
10 LOAD_CONST 2 ('f')
12 MAKE_FUNCTION 0
14 STORE_NAME 2 (f)
16 LOAD_CONST 3 (None)
18 RETURN_VALUE

Disassembly of <code object f at 0x00000200F257CF60,line 5>:
6 0 LOAD_CONST 0 (None)
2 RETURN_VALUE

上述程式碼中編譯生成了兩個code物件,一個代表全域性作用域,另一個代表函式f。

code物件儲存了變數,常量(常量字面量)以及編譯結果。code物件用常量表來儲存常量,考慮到一個常量可能出現多次,在一張表上儲存一個常量多次太過於奢侈。所以code物件對每個常量只儲存一次,在需要引用它的地方使用它在常量表的位置作為常量的表示。在上述編譯結果中可以看到,"1 2"這個字串常量使用了兩次,編譯的程式碼為"LOAD_CONST 0",這裡的0就是"1 2"在常量表當中的位置。

由於編譯的這個特性,在同一個code物件中的變數,如果它們引用了同一個常量,那麼無論這個常量有沒有緩衝機制,它們引用的都是同一個物件。

a = "12"
b = "12"
c = "1 2"
d = "1 2"
e = 257
f = 257
g = 2424234234234234
h = 2424234234234234
print(a is b,c is d,e is f,g is h)

輸出結果

True True True True

這個例子說明,在同一個code物件當中,常量(字面量)僅一份,這與緩衝機制無關,是編譯特性。所以對於上述那個奇怪的問題就可以解釋了,當i,j在同一個code物件(同一個作用域)中引用常量"1 2",它們引用的都是同一個物件。而當在python命令列中分開執行時,對於每一條語句,都是一個單獨的code物件,這時起作用的是字串intern機制,上述執行結果說明,字串intern機制對"12"進行了intern,而對"1 2"沒有進行intern。

編譯機制與小整數物件池對比

i = 257
j = 257
a = i - 1
b = i - 1
c = i + 1
d = i + 1
print(i is j,a is b,c is d)

輸出結果

True True False

i和j引用同一個常量,這是編譯機制,所以i與j指向同一個整數物件,後面a和b雖然相等,但不引用常量,此時啟用小整數物件池,a,b都等於256,在物件池中,所以a,b引用同一個物件,後面c,d不在物件池中,所以兩者物件不同。

這裡有一點需要注意,沒有變數參與的運算會被編譯器直接優化成對應的常量,進而儲存進常量表中。

字串intern機制與字元緩衝池
在編譯過程中,字串intern機制將所有的變數名進行intern,但對常量進行的intern有一點特殊的限制。能夠intern的常量必須只包含[a-zA-Z0-9_],即字母數字加下劃線,如果含有其他字元,就不會intern。在執行過程中,通過計算得到的字串不會intern。

字串有一個和小整數物件池相似的字元緩衝池,用於在執行過程中快取單個字元,所以計算得到的字串雖然不會intern,但如果是單個字元,就會使用到字元緩衝池。

k = "bbb"
a = k[0]
b = k[0]
c = k[1:]
d = k[1:]
print(a,d)
print(a is b,c is d)

輸出結果

b bb
True False

可以看到,a和b確實指向同一個物件,而c和d指向不同物件,這就是字元緩衝池。

編譯機制與字串intern對比

i = "1 2"
j = "12"
k = "__fjdslfjaskfas"

ii = "1 2"
jj = "12"
kk = "__fjdslfjaskfas"

def f():
  a = "1 2"
  b = "12"
  c = "__fjdslfjaskfas"
  return a is i,b is j,c is k

print("Code:",i is ii,j is jj,k is kk)
print(f"intern: {f()}")

輸出結果

Code: True True True
intern: (False,True,True)

i包含空格,包含空格的常量不會被intern,而其他兩個常量不包含其他字元,所以會被intern。

總結

1. python程式碼被編譯成code物件,通常一個code物件對應於一個作用域,作用域中重複出現的變數名以及常量在code中只儲存一次。

2. 字串intern機制主要作用於編譯過程,在編譯收集完變數和常量時,對變數和常量進行intern,而後構建一個code物件。

3. 字串intern對常量的intern有限制,能夠intern的常量必須只包含[a-zA-Z0-9_],即字母數字加下劃線,如果含有其他字元,就不會intern。

4. 小整數物件池和字元緩衝池都是作用於執行過程中,python快取小的整數和字元,當有變數使用這些物件時,不用額外建立物件。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。