天天看點

Python中 '==' 與'is' 以及它們背後的故事

摘要

  比較判斷邏輯是在代碼中經常使用的,在Python中常用 '==' 和 is 來做比較判斷。

  • ==  :  雙等号是用來比較變量所指向記憶體單元中的值是否相等,它隻關心值,并不在意值的記憶體位址,也就是說可以是兩個不同記憶體位址的值相等。
  • is    :  它用來比較兩個變量是不是指向同一個記憶體單元,雖然它也可以比較值,但是它更加關心的是記憶體位址是否一樣,當然記憶體位址一樣值也就是一樣的。

關于整數

# 按照邏輯,下面的代碼很正常
>>> a = 1
>>> b = 1
>>> a == b
True
>>> a is b
True
>>> id(a)
1570522768
>>> id(b)
1570522768
# 下面就是颠覆認知的時刻
>>> a = 1000
>>> b = 1000
>>> a == b
True
>>> a is b
False
>>> id(a)
81183344
>>> id(b)
81183376      

  是的,兩個相同值的變量,記憶體位址不一樣了。當然産生這個現象的前提條件是用python指令行去執行,而不是用pycharm之類的編輯器。其根本原因也就是python解釋器的問題,涉及到python的垃圾回收機制。上面現象的原因是因為一個叫做小整數對象池的東西。

   Python為了優化速度,會把 [-5, 256] 之間的資料提前存放在小整數池中,如果程式使用到小整數池中的資料,是不會開辟新的記憶體空間去建立,而是指向對象池中的同一份資料,也就是說有N個變量等于1的話,那麼這N個變量的記憶體位址都會指向小整數池中的1位置。小整數池的使用是為了避免整數頻繁申請和銷毀記憶體空間。小整數池是提前建立好的,不會被垃圾回收。

  當資料超出小整數池後,也就是範圍到了大整數對象池中了,系統每次都會申請一塊新記憶體來存儲資料,這個'is'不等于'=='的現象也就不存在了。

  pycharm中,每次運作是所有代碼都加載到記憶體中,屬于一個整體,并不存在這個現象。

關于字元串 

# 先來個正常的
>>> a = 'qwe'
>>> b = 'qwe'
>>> a == b
True
>>> a is b
True
>>> id(a)
81797024
>>> id(b)
81797024
#  感覺沒什麼變化,那就加長一些
>>> a = 'q' * 20
>>> b = 'q' * 20
>>> a is b
True
>>> a == b
True
# 在長點就不一樣了
>>> b = 'q' * 21
>>> a = 'q' * 21
>>> a is b
False
>>> a == b
True
>>> id(a)
81811696
>>> id(b)
81811600      

  産生原因:Python的intern機制。

  簡單了解有點像緩存的意思,當需要使用相同的字元串時(變量指派),直接從緩存中拿出來用而不是重新建立,這樣可以避免頻繁的建立和銷毀,提升效率,節約記憶體。缺點是拼接字元串,對字元串修改之類的影響性能。因為是不可變的,是以對字元串修改不是inplace操作,而是建立對象。這也就是拼接字元串的時候不建議是用 '+' 方法,而是推薦用join 函數,join函數是先計算出所有字元串的長度,然後一一拷貝,而隻建立一次對象。每個'+'方法都是建立一次新對象。當字元串長度超過20時,也不會使用intern機制。

  并不是所有的字元串都會采用intern機制。隻包含下劃線,字母(包含大小寫),數字的字元串才會被intern。空格和一些特殊字元都不在内。也就是說字元串中如果包含空格和其他一些特殊符号(除去下劃線),python都不會應用intern機制,而是直接開辟新的記憶體空間去存儲。

# 注意下面這種看似合理的字元串intern
>>> 'ab' + 'c' is 'abc'         #  這裡的字元串,'ab' + 'c' 是在complie time 求值的,被替換成了'abc'
True
>>> n1 = 'ab'
>>> n2 = 'abc'
>>> n1 + 'c' is n2               # n1 + 'c'  是在run-time拼接,導緻沒有被自動intern
False
>>> n1 + 'c' is 'abc'
False
>>> n1 + 'c' == 'abc'
True
>>> n1 + 'c' == n2
True