翻譯:老齊python
與本文內容配套的圖書:《跟老齊學Python:輕鬆入門》《Python大學實用教程》,各大電商平臺有售。安全
Python中的is
和==
是不同的。使用is
能夠比較數字,代碼也正常運行。也有人說is
比==
要更快,或者你可能以爲它看起來更像Python。然而,重要的是要記住這些運算符的行爲並不徹底相同。bash
==
用於比較兩個對象的值是否相等,而is
檢查兩個變量是否指向內存中的同一個對象。在大多數狀況下,這意味着你應該使用==
和!=
,除非與None
進行比較。app
在本文中,你將學習:ide
==
和is
比較對象is
和is not
比較值會致使意外__eq__()
類方法來定義相等運算符行爲is
和is not
的應用is
和is not
用來比較兩個對象。在CPython中,比較的是對象的內存地址。Python中的一切都是對象,每一個對象都存儲在特定的內存位置, is
和is not
'檢查兩個變量是否引用內存中的同一個對象。函數
注意: 記住,具備相同值的對象可能存儲在不一樣的內存地址中。性能
你可使用id()
來檢查一個對象的內存地址:學習
>>> help(id)
Help on built-in function id in module builtins:
id(obj, /)
Return the identity of an object.
This is guaranteed to be unique among simultaneously existing objects.
(CPython uses the object's memory address.) >>> id(id) 2570892442576 複製代碼
最後一行顯示存儲內置函數id
自己的內存地址。優化
一般,具備相同值的對象在默認狀況下具備相同的id。例如,數字-5到256在CPython中被保存,每一個數字都存儲在內存中單一且固定的位置,這爲經常使用整數節省了內存。ui
你可使用sys.intern()
來保存字符串以提升性能,此函數容許你比較它們的內存地址,而不是對字符串裏的字符進行逐個比較:
>>> from sys import intern
>>> a = 'hello world'
>>> b = 'hello world'
>>> a is b
False
>>> id(a)
1603648396784
>>> id(b)
1603648426160
>>> a = intern(a)
>>> b = intern(b)
>>> a is b
True
>>> id(a)
1603648396784
>>> id(b)
1603648396784
複製代碼
變量a
和b
最初指向內存中的兩個不一樣對象,如它們的不一樣id所示。使用intern
後,a
和b
則指向內存中的同一對象。在原來的操做中,兩個'hello world'
分別在新的內存位置建立對象,可是,對一樣的字符串執行intern
後,後面所建立的字符串所指向的內存地址與第一個'hello world'
的內存地址相同。
注意:即便對象的內存地址在任何給定的時間都是惟一的,但這個內存地址在同一代碼的不一樣運行過程當中是不一樣的,而且取決於CPython的版本和運行代碼的計算機。
默認狀況下,具備intern
效果的對象是None
、True
、False
和簡單字符串。請記住,大多數狀況下,具備相同值的不一樣對象將存儲在不一樣的內存地址中,這意味着你不該該使用is
來比較值。
Python將經常使用的值(例如,整數-5到256)默認保存在內存中,從而節省內存開支。下面的代碼向你展現了爲何只有一些整數具備固定的內存地址:
>>> a = 256
>>> b = 256
>>> a is b
True
>>> id(a)
1638894624
>>> id(b)
1638894624
>>> a = 257
>>> b = 257
>>> a is b
False
>>> id(a)
2570926051952
>>> id(b)
2570926051984
複製代碼
最初,a
和b
引用內存中的同一個存儲對象,但當它們的值超出經常使用整數的範圍(從-5到256)時,它們就存儲在不一樣的內存地址中。
用賦值運算符(=
)使一個變量等於另外一個變量時,可使這些變量指向內存中的同一對象。這可能會致使可變對象出現意外行爲:
>>> a = [1, 2, 3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3, 4]
>>> id(a)
2570926056520
>>> id(b)
2570926056520
複製代碼
剛纔發生了什麼? 你向a
添加了一個新元素,可是如今b
也包含了這個元素! 在b = a
這一行,設置b指向與a相同的內存地址,這樣兩個變量就都引用相同的對象。
若是你獨立地定義這些列表,那麼它們就被存儲在不一樣的內存地址中,並獨立地運行:
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False
>>> id(a)
2356388925576
>>> id(b)
2356388952648
複製代碼
由於a
和b
如今引用內存中的不一樣對象,因此更改一個對象不會影響另外一個對象。
==
和!=
比較對象回想一下,具備相同值的對象一般存儲在不一樣的內存地址中。若是要檢查兩個對象是否具備相同的值,而無論它們存儲在內存中的位置,使用運算符=
和!=
。在絕大多數狀況下,這就是你想作的。
在下面的示例中,b
是a
的副本(a
是可變對象,如列表或字典)。兩個變量都有相同的值,但它們將各自存儲在不一樣的內存地址:
>>> a = [1, 2, 3]
>>> b = a.copy()
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> a == b
True
>>> a is b
False
>>> id(a)
2570926058312
>>> id(b)
2570926057736
複製代碼
a
和 b
如今存儲在不一樣的內存地址,所以a is b
再也不返回True。可是,a==b
返回True,由於兩個對象具備相同的值。
==
的魔力體如今該符號左邊對象所具備的__eq__()
方法中。
這是一個神奇的類方法,每當這個類的一個實例與另外一個對象進行比較時都會調用它。若是未實現此方法,則默認狀況下==
比較兩個對象的內存地址。
做爲練習,建立一個繼承str
的SillyString
類並實現__eq__()
,以比較此字符串的長度是否與另外一個對象的長度相同:
class SillyString(str):
# This method gets called when using == on the object
def __eq__(self, other):
print(f'comparing {self} to {other}')
# Return True if self and other have the same length
return len(self) == len(other)
複製代碼
如今,用'hello world'
建立的SillyString實例應該等於用'world hello'
建立的實例,甚至等於長度相同的任何其餘對象:
>>> # Compare two strings
>>> 'hello world' == 'world hello'
False
>>> # Compare a string with a SillyString
>>> 'hello world' == SillyString('world hello')
comparing world hello to hello world
True
>>> # Compare a SillyString with a list
>>> SillyString('hello world') == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
comparing hello world to [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
True
複製代碼
固然,對於一個字符串形式的對象來講,這是愚蠢的行爲,但它確實說明了當你使用==
比較兩個對象時會發生什麼。對於!=
,則是經過實現特定的__ne__()
方法,給出逆響應。
上面的示例還清楚地向你展現了爲何更好的作法是用 is
來比較None
,而不是使用==
運算符。is
比較的是內存地址,因此它不只更快,並且更安全,由於它不依賴於任何__eq__()
方法的邏輯。
根據經驗,你應該經常使用==
和!=
,除非與None
進行比較:
==
和!=
比較對象的相等性。這裏,你一般比較兩個對象的值。若是要比較兩個對象是否具備相同的內容,而不關心它們存儲在內存中的位置,則須要下面的作法。is
和is not
。這裏,你要比較兩個變量是否指向內存中的同一個對象。這些運算符的主要用例是與None進行比較。與使用類方法相比,按內存地址與None進行比較更快、更安全。具備相同值的變量一般存儲在不一樣的內存地址中,這意味着你應該使用==
和!=
來比較他們的值。只有當你想檢查兩個變量是否指向同一個內存地址時,才使用is
和is not
。
在本文中,你瞭解了==
和!=
比較兩個對象的值,而is
和is not
比較兩個變量是否引用內存中的同一個對象。若是你牢記這一區別,那麼應該可以防止代碼中出現意外行爲。
你還能夠看看如何使用sys.intern()
來優化字符串的內存使用和比較,儘管Python可能已經在幕後自動爲你處理了這一問題。
如今你已經瞭解了兩種比較的幕後操做,能夠嘗試編寫本身的__eq__()
方法,這些方法定義了在使用==
運算符時如何比較該類的實例。去應用關於Python比較運算符的這些新知識吧!