【譯】Python中的 `!=`與`is not`不一樣

翻譯:老齊python

與本文內容配套的圖書:《跟老齊學Python:輕鬆入門》《Python大學實用教程》,各大電商平臺有售。安全


Python中的is==是不同的。使用is能夠比較數字,代碼也正常運行。也有人說is==要更快,或者你可能以爲它看起來更像Python。然而,重要的是要記住這些運算符的行爲並不徹底相同。bash

==用於比較兩個對象的值是否相等,而is檢查兩個變量是否指向內存中的同一個對象。在大多數狀況下,這意味着你應該使用==!=,除非與None進行比較。app

在本文中,你將學習:ide

  • 對象相等和同一性的區別是什麼
  • 什麼時候使用==is比較對象
  • 這些Python運算符的原理是什麼
  • 爲何使用isis not比較值會致使意外
  • 如何編寫自定義的__eq__()類方法來定義相等運算符行爲

介紹isis not的應用

isis not用來比較兩個對象。在CPython中,比較的是對象的內存地址。Python中的一切都是對象,每一個對象都存儲在特定的內存位置, isis 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
複製代碼

變量ab最初指向內存中的兩個不一樣對象,如它們的不一樣id所示。使用intern後,ab則指向內存中的同一對象。在原來的操做中,兩個'hello world'分別在新的內存位置建立對象,可是,對一樣的字符串執行intern後,後面所建立的字符串所指向的內存地址與第一個'hello world'的內存地址相同。

注意:即便對象的內存地址在任何給定的時間都是惟一的,但這個內存地址在同一代碼的不一樣運行過程當中是不一樣的,而且取決於CPython的版本和運行代碼的計算機。

默認狀況下,具備intern效果的對象是NoneTrueFalse和簡單字符串。請記住,大多數狀況下,具備相同值的不一樣對象將存儲在不一樣的內存地址中,這意味着你不該該使用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
複製代碼

最初,ab引用內存中的同一個存儲對象,但當它們的值超出經常使用整數的範圍(從-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
複製代碼

由於ab如今引用內存中的不一樣對象,因此更改一個對象不會影響另外一個對象。

==!=比較對象

回想一下,具備相同值的對象一般存儲在不一樣的內存地址中。若是要檢查兩個對象是否具備相同的值,而無論它們存儲在內存中的位置,使用運算符=!=。在絕大多數狀況下,這就是你想作的。

當對象副本相等但不相同時

在下面的示例中,ba的副本(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
複製代碼

ab 如今存儲在不一樣的內存地址,所以a is b再也不返回True。可是,a==b返回True,由於兩個對象具備相同的值。

相等比較如何起做用

==的魔力體如今該符號左邊對象所具備的__eq__()方法中。

這是一個神奇的類方法,每當這個類的一個實例與另外一個對象進行比較時都會調用它。若是未實現此方法,則默認狀況下==比較兩個對象的內存地址。

做爲練習,建立一個繼承strSillyString類並實現__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進行比較:

  • 使用==!=比較對象的相等性。這裏,你一般比較兩個對象的值。若是要比較兩個對象是否具備相同的內容,而不關心它們存儲在內存中的位置,則須要下面的作法。
  • 若是要比較對象的惟一標識,請使用isis not。這裏,你要比較兩個變量是否指向內存中的同一個對象。這些運算符的主要用例是與None進行比較。與使用類方法相比,按內存地址與None進行比較更快、更安全。

具備相同值的變量一般存儲在不一樣的內存地址中,這意味着你應該使用==!=來比較他們的值。只有當你想檢查兩個變量是否指向同一個內存地址時,才使用isis not

結論

在本文中,你瞭解了==!=比較兩個對象的值,而isis not比較兩個變量是否引用內存中的同一個對象。若是你牢記這一區別,那麼應該可以防止代碼中出現意外行爲。

你還能夠看看如何使用sys.intern()來優化字符串的內存使用和比較,儘管Python可能已經在幕後自動爲你處理了這一問題。

如今你已經瞭解了兩種比較的幕後操做,能夠嘗試編寫本身的__eq__()方法,這些方法定義了在使用==運算符時如何比較該類的實例。去應用關於Python比較運算符的這些新知識吧!

原文連接:realpython.com/python-is-i…

相關文章
相關標籤/搜索