詳解python中的 is 操作符
由知乎上的一個問題所啟發,就寫了這麼一篇文章。大家可以與Java中的 == 操作符相互印證一下,加深一下對引用和對象的理解。原問題:Python為什麼直接運行和在命令行運行同樣語句但結果卻不同,他們的緩存機制不同嗎?
其實,高票答案已經說得很詳細了。我只是再補充一點而已。
is 操作符是Python語言的一個內建的操作符。它的作用在於比較兩個變數是否指向了同一個對象。
與 == 的區別
class A(): def __init__(self, v): self.value = v def __eq__(self, t): return self.value == t.valuea = A(3)b = A(3) print a == bprint a is b
這個結果是True,False。因為我們重寫了__eq__方法就使得a, b在比較的時候,只比較它們的value即可。只要它們的value相等,那麼a, b就是相等的。
而 is 操作符是判斷兩個變數是否引用了同一個對象。
同一個對象?
is 的用法說起來其實挺簡單的,但是真正用起來,它的難點恰恰就在於判斷哪些對象是同一個對象。
看下面的幾個測試,先不看結果,自己能答對多少?
a = 10b = 10print a is ba = 10.0b = 10.0print a is ba = 10def f(): return 10print f() is aa = 1000def f(): return 1000print f() is aa = 10.0def f(): return 10.0print f() is a
嗯。這個結果是True, True, True, False, False。你答對了嗎?
這個結果中牽扯到兩個問題:第一,就是小整數的緩存,第二,就是pyc文件中CodeObject的組織問題。
Python中把-127到128這些小整數都緩存了一份。這和Java的Integer類是一樣的。所以,對於-127到128之間的整數,整個Python虛擬機中就只有一個實例。不管你什麼時候,什麼場景下去使用 is 進行判斷,都會是True,所以我們知道了這兩個測試一定會是True:
a = 10b = 10print a is ba = 10def f(): return 10print f() is a
接著,我們重點看下,這兩個測試:
a = 10.0b = 10.0print a is ba = 10.0def f(): return 10.0print f() is a
為什麼一個是True,一個是False。要探究這個問題,就要從位元組碼的角度去分析了。我們先把這個文件編譯一下:
python -m compileall testis.py
然後再使用這個工具查看一下位元組碼文件:https://github.com/hinus/railgun/blob/master/src/main/python/rgparser/show.py
得到這樣的輸出:
<code> <argcount> 0 </argcount> <nlocals> 0</nlocals> <stacksize> 2</stacksize> <flags> 0040</flags> <code> 6400005a00006400005a01006500006501006b080047486400005a000064 01008400005a02006502008300006500006b0800474864020053 </code> <dis> 1 0 LOAD_CONST 0 (10.0) 3 STORE_NAME 0 (a) 2 6 LOAD_CONST 0 (10.0) 9 STORE_NAME 1 (b) 3 12 LOAD_NAME 0 (a) 15 LOAD_NAME 1 (b) 18 COMPARE_OP 8 (is) 21 PRINT_ITEM 22 PRINT_NEWLINE 5 23 LOAD_CONST 0 (10.0) 26 STORE_NAME 0 (a) 6 29 LOAD_CONST 1 (<code object f>) 32 MAKE_FUNCTION 0 35 STORE_NAME 2 (f) 8 38 LOAD_NAME 2 (f) 41 CALL_FUNCTION 0 44 LOAD_NAME 0 (a) 47 COMPARE_OP 8 (is) 50 PRINT_ITEM 51 PRINT_NEWLINE 52 LOAD_CONST 2 (None) 55 RETURN_VALUE </dis> <names> (a, b, f)</names> <varnames> ()</varnames> <freevars> ()</freevars> <cellvars> ()</cellvars> <filename> testis.py</filename> <name> <module></name> <firstlineno> 1</firstlineno> <consts> 10.0 <code> <argcount> 0 </argcount> <nlocals> 0</nlocals> <stacksize> 1</stacksize> <flags> 0043</flags> <code> 64010053</code> <dis> 7 0 LOAD_CONST 1 (10.0) 3 RETURN_VALUE </dis> <names> ()</names> <varnames> ()</varnames> <freevars> ()</freevars> <cellvars> ()</cellvars> <filename> testis.py</filename> <name> f</name> <firstlineno> 6</firstlineno> <consts> None 10.0 </consts> <lnotab> 0001</lnotab> </code> None </consts> <lnotab> 060106010b0206010902</lnotab></code>
大家注意看,整個python文件其實就是一個大的<code>對象,f 所對應的那個函數也是一個<code>對象,這個code對象做為整體是大的<code>對象的consts域里的一個const項。再注意,在大<code>對象里,有10.0這樣的一個const項,f 這個<code>對象所對應的conts里呢,也有一個10.0這個浮點數。
當python在載入這個文件的時候,就會完成主<code>里的10.0這個浮點數的載入,生成一個PyFloatObject。也就是說靜態的pyc文件的常量表在被載入以後,就變成了內存中的常量表,文件的表裡的10.0就變成了內存中的一個PyFloatObject。所以,a, b兩個變數都會引用這個PyFloatObject。
但是 f 里的那個10.0呢?它是要等到MAKE_FUNCTION被調用的時候才會真正地初始化。做為 f 方法的返回值,它必然與我們之前所說的主<code>里的10.0不是同一個對象了。
本質上講,這是Python的一個設計缺陷(例如Java以一個文件為編譯單元,共享同一個常量池就會減輕這個問題。但如果跨文件使用 == 操作符,也會出現同樣的問題。仍然沒有解決這個問題。實際上,我自己也不知道該怎麼解決這個問題。)我們應該盡量避免 is 的這種用法。始終把 is 的用法限制在本文的第一個例子中。這樣相對會安全一些。
更多編程相關的內容,請關注我的公眾號:
我的公眾號
推薦閱讀:
※用Python做地圖投影 - 多面孔的世界
※Flask中的請求上下文和應用上下文
※一起來寫pydu——常用數據結構工具集庫
※Python入門 文件讀取與寫入
TAG:Python |