Python中is和==的區別及intern機制

寫在前面

本來今天是更新一篇關於字符串的文章,但是還沒寫完,先更新一篇關於is和==區別的文章,因為字符串文章中會涉及到本篇的部分內容。另外最近會勤於更新,大家不要催更了......

is和==的區別

1、is 比較的是地址,注意小整數池和字符串,一般重複創建的時候會指向同一個對象。

t1 = 123
t2 = 123
print(t1 is t2) # True
print(id(t1), id(t2)) # 1789477024 1789477024
print("*********************************")
l1 = [1,2,3]
l2 = [1,2,3] # False,因為會重新聲明一個對象
# l2 = l1 # True,因為是賦值符號,就是指針指向
print(l1 is l2) # False
print(id(l1), id(l2)) # 56509864 56511024

因為在python中存在intern機制,適用於小整數池和字符串,也就是說創建對象的時候會先在小整數池中查找,如果存在就返回,否則就會新建對象。記住僅僅適用於小整數池和字符串,對於list是不使用的

2、== 比較的是值,其實==重載了對象的__eq__方法,而這個方法比較的是對象的值。

t1 = 123
t2 = 123
print(t1 == t2) # True
print("*********************************")
l1 = [1,2,3]
l2 = [1,2,3]
print(l1 == l2) # True

因為我們在使用==對兩個對象進行值判斷的時候,其實就是調用了__eq__這個魔法函數,查看一下str中的__eq__魔法函數:

 def __eq__(self, *args, **kwargs): # real signature unknown
""" Return self==value. """
pass

再來看一下list對象中的__eq__魔法函數:

 def __eq__(self, *args, **kwargs): # real signature unknown
""" Return self==value. """
pass

你會發現兩者是一模一樣,因此==就是對值進行判斷,只要值相等就是True。

我們再來看一個例子,在判斷類的類別時,是使用is,而不是==,原因在於type(obj)返回的是某個類,而類是一個全局唯一的變量,這種做法可以避免很多未知的錯誤:

class Person:
pass
person = Person()
if type(person) is Person: # 記住這裡最好使用is,不要使用==

print("yes")

python的intern機制

由於變量的存儲機制,python增加了字符串的intern機制,也就是說值同樣的字符串對象(整數也實用)僅僅會保存一份,而且是共用的,這也決定了字符串必須是不可變對象,其實仔細想一想,就和數值類型一樣,同樣的數值僅僅要保存一份即可了,不是必須用不同對象來區分。

我們來看幾個例子加深我們的認識,開啟Python的IDE,必須使用Python自帶的交互式環境,不能使用Pycharm:

>>> a = 'test'
>>> b = 'tes' + 't'
>>> print(id(a), id(b))
>>> print(a is b)
# 運行結果:
# 51379584 51379584
# True

從結果中可以看出它們實際上是同一個對象,所以用is判斷就是正確。


intern機制優點是:在創建新的字符串對象時,會先在緩存池裡面找是否有已經存在的值相同的對象(標識符,即只包含數字、字母、下劃線的字符),如果有則直接拿過來用(引用),避免頻繁的創建和銷燬內存,提升效率。

缺點是在拼接字符串時或者在改動字符串時會極大的影響性能。原因是字符串在Python當中是不可變對象,所以對字符串的改動不是inplace(原地)操作,需要新開闢內存地址,即新建對象。這也是為什麼拼接字符串的時候不建議用‘+’而是用join()。join()是先計算出全部字符串的長度,然後再一一拷貝,僅僅創建一次對象。


再來看這個例子:

#這裡只是演示了包含空格的情況,實際上除了字母,數字,下劃線以外的都是這個情況
>>> j = "hello world"
>>> k = "hello world"
>>> print(id(j), id(k))
# 93423920 95800760
>>> print(j is k)
# False

你可能會好奇怎麼是False,因為這裡的字符串裡面含有空格,導致不會觸發intern機制。 也就是說字符串中沒有空格則會默認開啟intern機制,有則就不會開啟了。

你現在可能會好奇Python為什麼會這麼做呢?因為Python的內置函數intern()能顯式的對任意字符串進行intern,就說明並不是實現難度的問題,解決這個問題最好是查看Python的源碼,可以找到答案,在源代碼StringObject.h中的註釋能夠找到:

/* … … This is generally restricted tostrings that “looklike” Python identifiers, although the intern() builtincan be used to force interning of any string … … */

也就是說intern機制僅僅對那些看起來像是Python標識符的字符串對象才會觸發。

我們再來看一個例子:

>>> 'tes'+'t' is 'test'
>>> True
>>> a = 'tes'
>>> a + 't' is 'test'
>>> False

你可能會問為什麼是這樣?它們不都只是包含字母嗎,沒有空格應該是被主動intern的呀? 的確是不錯,但是你忽略了一個事實。在第一個例子中,‘tes’ + ‘t’是在compile-time(編譯時)求值的,被替換成了’test’,而在第二個例子中,a + ‘t’是在run-time(運行時)拼接的,導致沒有主動觸發intern機制。

我們再來看一下Python中的小整數的例子:

>>> a = 257
>>> b= 257
>>> print(id(a), id(b))
94149312 94149488
>>> a = 256
>>> b = 256
>>> print(id(a), id(b))
1790200048 1790200048
>>> a = -5
>>> b = -5
>>> print(id(a), id(b))
1790195872 1790195872
>>> a = -6
>>> b =-6
>>> print(id(a), id(b))

94148784 94149296

在Python的小整數池[-5,256]這個範圍內也是默認開啟intern機制,也就是在創建對象的時候會先判斷整數是否在小整數池中,是的話就共用同一個對象,否則就新建對象。

總結一下

1、is 比較的是地址,注意小整數池和字符串,一般重複創建的時候會指向同一個對象。

2、== 比較的是值,其實==重載了對象的__eq__方法,而這個方法比較的是對象的值。

3、單詞,即Python標識符是不可修改的,默認開啟intern機制,是共用對象,當引用計數為0時自動被回收。

4、字符串(包含了除Python標識符以外的字符),不可修改,默認沒有開啟intern機制,也是當引用計數為0時自動被回收。

5、極少數特殊情況下(如上述最後一個例子時),也不會主動開啟intern機制。

6、在Python的小整數池[-5,256]這個範圍內也是默認開啟intern機制。

Python中is和==的區別及intern機制


分享到:


相關文章: