Python解惑:整數比較 is ==的比較

天府雲創發表於2017-12-08

在 Python 中一切都是物件,毫無例外整數也是物件,物件之間比較是否相等可以用==,也可以用is==is操作的區別是:

  • is比較的是兩個物件的id值是否相等,也就是比較倆物件是否為同一個例項物件,是否指向同一個記憶體地址。
  • ==比較的是兩個物件的內容是否相等,預設會呼叫物件的__eq__()方法。

清楚is==的區別之後,對此也許你有可能會遇到下面的這些困惑,於是就有了這樣一篇文章,試圖把Python中一些隱晦的東西趴出來,希望對你有一定的幫助。我們先來看兩段程式碼:

片段一:

>>> a = 256
>>> b = 256
>>> a == b
True
>>>

片段二:

>>> a = 256
>>> b = 256
>>> a is b
True
>>>

在互動式命令列執行上面兩段程式碼,程式碼片段一中的a==b返回True很好理解,因為兩個物件的值都是256,對於片段二,a is b也返回True,這說明a和b是指向同一個物件的,可以檢查一下他們的id值是否相等:

>>> id(a)
8213296
>>> id(b)
8213296
>>> 

結果證明他倆的確是同一個物件,指向的是同一個記憶體地址。那是不是所有的整數物件只要兩個物件的值(內容)相等,它們就是同一個例項物件呢?換句話說,對於整數物件只要==返回Trueis操作也會返回True嗎?帶著這個問題來看下面這兩段程式碼:

片段一:

>>> a = 257
>>> b = 257
>>> a == b
True
>>>

片段二:

>>> a = 257
>>> b = 257
>>> a is b
False
>>>

對於257,a is b返回的竟然是False,結果可能在你的意料之中,也有可能出乎你的意料,但不管怎麼,我們還是要刨根問底,找出問題的真相。

解惑一

出於對效能的考慮,Python內部做了很多的優化工作,對於整數物件,Python把一些頻繁使用的整數物件快取起來,儲存到一個叫small_ints的連結串列中,在Python的整個生命週期內,任何需要引用這些整數物件的地方,都不再重新建立新的物件,而是直接引用快取中的物件。Python把這些可能頻繁使用的整數物件規定在範圍[-5, 256]之間的小物件放在small_ints中,但凡是需要用些小整數時,就從這裡面取,不再去臨時建立新的物件。因為257不再小整數範圍內,因此儘管a和b的值是一樣,但是他們在Python內部卻是以兩個獨立的物件存在的,各自為政,互不干涉。


弄明白第一個問題後,我們繼續在Python互動式命令列中寫一個函式,再來看下面這段程式碼:

片段一:

>>> c = 257
>>> def foo():
...     a = 257
...     b = 257
...     print a is b
...     print a is c
... 
>>> foo()
True
False

呃,什麼情況,是的,你沒看錯,片段一中的這段程式碼 a、b 值都是257的情況下,出現了a is b返回True,而a is c 返回的False,a、b、c的值都為257,為什麼會出現不同的結果呢?這對於剛剛好不容易建立起來的認知就被徹底否決了嗎,那這段程式碼中究竟發生了什麼?難道解惑一中的結論是錯誤的嗎?

解惑二

A Python program is constructed from code blocks. A block is a piece of Python program text that is executed as a unit. The following are blocks: a module, a function body, and a class definition. Each command typed interactively is a block. A script file (a file given as standard input to the interpreter or specified as a command line argument to the interpreter) is a code block. A script command (a command specified on the interpreter command line with the ‘-c‘ option) is a code block. structure-of-a-program

為了弄清楚這個問題,我們有必要先理解程式程式碼塊的概念。Python程式由程式碼塊構成,程式碼塊作為程式的一個最小基本單位來執行。一個模組檔案、一個函式體、一個類、互動式命令中的單行程式碼都叫做一個程式碼塊。在上面這段程式碼中,由兩個程式碼塊構成,c = 257作為一個程式碼塊,函式foo作為另外一個程式碼塊。Python內部為了將效能進一步的提高,凡是在一個程式碼塊中建立的整數物件,如果存在一個值與其相同的物件於該程式碼塊中了,那麼就直接引用,否則建立一個新的物件出來。Python出於對效能的考慮,但凡是不可變物件,在同一個程式碼塊中的物件,只有是值相同的物件,就不會重複建立,而是直接引用已經存在的物件。因此,不僅是整數物件,還有字串物件也遵循同樣的原則。所以 a is b就理所當然的返回True了,而ca不在同一個程式碼塊中,因此在Python內部建立了兩個值都是257的物件。為了驗證剛剛的結論,我們可以借用dis模組從位元組碼的角度來看看這段程式碼。

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (257)
              3 STORE_FAST               0 (a)

  3           6 LOAD_CONST               1 (257)
              9 STORE_FAST               1 (b)

  4          12 LOAD_FAST                0 (a)
             15 LOAD_FAST                1 (b)
             18 COMPARE_OP               8 (is)
             21 PRINT_ITEM          
             22 PRINT_NEWLINE       

  5          23 LOAD_FAST                0 (a)
             26 LOAD_GLOBAL              0 (c)
             29 COMPARE_OP               8 (is)
             32 PRINT_ITEM          
             33 PRINT_NEWLINE       
             34 LOAD_CONST               0 (None)
             37 RETURN_VALUE

可以看出兩個257都是從常量池的同一個位置co_consts[1]獲取的。

總結

一番長篇大論之後,得出兩點結論:1、小整數物件[-5,256]是全域性直譯器範圍內被重複使用,永遠不會被GC回收。2、同一個程式碼塊中的不可變物件,只要值是相等的就不會重複建立新的物件。似乎這些知識點對日常的工作一點忙也幫不上,因為你根本不會用is來比較兩個整數物件的值是否相等。那為什麼還要拿出來討論呢?嗯,程式設計師學知識,不應該淺嘗輒止,要充分發揮死磕到底的精神。

相關文章