Python中is和==的區別

runstone發表於2020-09-26

python有很多運算子,其中is和==用的會比較多。此二者有些時候可以通用,作為相等比值來使用。但是有些時候二者是不能劃等號的:比如:

a = 257
b = 257
print(a is b)
print(a == b)

out1: False
out2: True

很顯然能看到a is b與a == b的返回值並不相等。這是為什麼呢?

首先在講is和==之前,我們要知道python的物件包含 三個基本要素id(身份標識),type(物件型別),value(物件值)

==比較運算子和is同一性運算子區別

  • ==是python標準運算子中的比較運算子,用來比較判斷兩個物件的value(值)是否相等,例如下面兩個字串間的比較:
>>> a = 'cheesezh'
>>> b = 'cheesezh'
>>> a == b
True
  • is也被叫做同一性運算子,這個運算子比較判斷的是物件間的唯一身份標識,也就是id是否相同。透過對下面幾個list間的比較,你就會明白is同一性運算子的工作原理:
>>> x = y = [4,5,6]
>>> z = [4,5,6]
>>> x == y
True
>>> x == z
True
>>> x is y
True
>>> x is z
False
>>>
>>> print id(x)
3075326572
>>> print id(y)
3075326572
>>> print id(z)
3075328140

前三個例子都是True,這什麼最後一個是False呢?x、y和z的值是相同的,所以前兩個是True沒有問題。至於最後一個為什麼是False,看看三個物件的id分別是什麼就會明白了。

  • 最後看一下這個,再來理解一下is和==的區別:
>>> a = 1 #a和b為數值型別
>>> b = 1
>>> a is b
True
>>> id(a)
14318944
>>> id(b)
14318944
>>> a = 'cheesezh' #a和b為字串型別
>>> b = 'cheesezh'
>>> a is b
True
>>> id(a)
42111872
>>> id(b)
42111872
>>> a = (1,2,3) #a和b為元組型別
>>> b = (1,2,3)
>>> a is b
False
>>> id(a)
15001280
>>> id(b)
14790408
>>> a = [1,2,3] #a和b為list型別
>>> b = [1,2,3]
>>> a is b
False
>>> id(a)
42091624
>>> id(b)
42082016
>>> a = {'cheese':1,'zh':2} #a和b為dict型別
>>> b = {'cheese':1,'zh':2}
>>> a is b
False
>>> id(a)
42101616
>>> id(b)
42098736
>>> a = set([1,2,3])#a和b為set型別
>>> b = set([1,2,3])
>>> a is b
False
>>> id(a)
14819976
>>> id(b)
14822256

總結

>>> x = -5
>>> y = -5
>>> x is y
True
>>> 
>>> x = -6
>>> y = -6
>>> x is y
False
>>> 
>>> x = 256
>>> y = 256
>>> x is y
True
>>> x = 257
>>> y = 257
>>> x is y
False
>>> 

# 數字限於[-5, 257)

事實上Python 為了最佳化速度,使用了小整數物件池,避免為整數頻繁申請和銷燬記憶體空間。而Python 對小整數的定義是 [-5, 257),只有數字在-5到256之間它們的id才會相等,超過了這個範圍就不行了,同樣的道理,字串物件也有一個類似的緩衝池,超過區間範圍內自然不會相等了。

總的來說,只有數值型和字串型,並且在通用物件池中的情況下,a is b才為True,否則當a和b是int,str,tuple,list,dict或set型時,a is b均為False。


擴充:

python在字串上也有一種機制,為python intern機制,又稱字串駐留。

  • 它是python直譯器用來提高字串使用的效率和使用效能做的最佳化。其原理就是同樣的字串物件僅僅會被儲存一份,放在一個字串儲蓄池中,是共用的。
  • intern機制的優缺點:
    • 優點:在建立新的字串時,會現在快取池中查詢是否有值相同的物件(識別符號,即只包含字母、數字、下劃線的字元),如果有,則直接拿過來用(引用),避免頻繁的建立和銷燬記憶體,提升效率。
    • 缺點:在拼接字串時,或者在改動字串時會極大的影響效能。原因是字串在Python當中是不可變物件,所以對字串的改動不是inplace(原地)操作,需要新開闢記憶體地址,新建物件。這也是為什麼拼接字串的時候不建議用‘+’而是用join()。join()是先計算出全部字串的長度,然後再一一複製,僅僅建立一次物件。
  • 儲蓄池本身是一個字典結構。直譯器內部對intern機制的使用是有考究的,有些情況會觸發,有些情況不會觸發。
  • 在shell環境中,通常僅僅包括字母、數字、下劃線的字串才會被intern,一般不超過20個字元。

看下面一些栗子:

# 字串僅包含字母、數字、下劃線
>>> a = 'wtf_'
>>> b = 'wtf_'
>>> a is b
True
>>> a = 'wtf_1'
>>> b = 'wtf_1'
>>> a is b
True
>>> b = 'abcdefghijklmnopqrstuvwxyz_1'
>>> a = 'abcdefghijklmnopqrstuvwxyz_1'
>>> a is b
True

# 字串拼接
>>> a = 'opq'
>>> b = 'o' + 'pq'
>>> id(a)
66832910
>>> id(b)
66832910

# 用變數去連線字串和字串連線字串是不一樣的
>>> a = 'opq'
>>> b = 'op'
>>> c = b + 'q'
>>> d = 'op' + 'q'
>>> a is c
False
>>> a is d
True
本作品採用《CC 協議》,轉載必須註明作者和本文連結
Stay hungry, stay foolish.

相關文章