[英]Why does the Python ** operator behave differently on arrays and scalars
[英]Why does the `is` operator behave differently in a script vs the REPL?
在python中,兩個代碼有不同的結果:
a = 300
b = 300
print (a==b)
print (a is b) ## print True
print ("id(a) = %d, id(b) = %d"%(id(a), id(b))) ## They have same address
但在 shell 模式下(交互模式):
>>> a = 300
>>> b = 300
>>> a is b
False
>>> id(a)
4501364368
>>> id(b)
4501362224
"is" 運算符有不同的結果。
當您在.py
腳本中運行代碼時,整個文件會在執行前編譯為代碼對象。 在這種情況下,CPython 能夠進行某些優化——比如為整數 300 重用同一個實例。
您還可以通過在更類似於執行腳本的上下文中執行代碼來在 REPL 中重現它:
>>> source = """\
... a = 300
... b = 300
... print (a==b)
... print (a is b)## print True
... print ("id(a) = %d, id(b) = %d"%(id(a), id(b))) ## They have same address
... """
>>> code_obj = compile(source, filename="myscript.py", mode="exec")
>>> exec(code_obj)
True
True
id(a) = 140736953597776, id(b) = 140736953597776
其中一些優化非常激進。 您可以修改腳本行b = 300
將其更改為b = 150 + 150
,CPython 仍會將b
“折疊”為相同的常量。 如果您對此類實現細節感興趣,請查看peephole.c
和 Ctrl+F 以獲取PyCode_Optimize
以及有關“consts 表”的任何信息。
相反,當您直接在 REPL 中逐行運行代碼時,它會在不同的上下文中執行。 每行都以“單”模式編譯,並且此優化不可用。
>>> scope = {}
>>> lines = source.splitlines()
>>> for line in lines:
... code_obj = compile(line, filename="<I'm in the REPL>", mode="single")
... exec(code_obj, scope)
...
True
False
id(a) = 140737087176016, id(b) = 140737087176080
>>> scope['a'], scope['b']
(300, 300)
>>> id(scope['a']), id(scope['b'])
(140737087176016, 140737087176080)
關於 CPython 及其行為,實際上有兩件事需要了解。 首先, [-5, 256]范圍內的小整數在內部被保留。 因此,任何落在該范圍內的值都將共享相同的 id,即使在 REPL 中也是如此:
>>> a = 100
>>> b = 100
>>> a is b
True
由於 300 > 256,它沒有被拘留:
>>> a = 300
>>> b = 300
>>> a is b
False
其次,在腳本中,文字被放入已編譯代碼的常量部分。 Python 足夠聰明地意識到,由於a
和b
都引用文字300
並且300
是一個不可變對象,它可以繼續引用相同的常量位置。 如果您稍微調整一下腳本並將其編寫為:
def foo():
a = 300
b = 300
print(a==b)
print(a is b)
print("id(a) = %d, id(b) = %d" % (id(a), id(b)))
import dis
dis.disassemble(foo.__code__)
輸出的開始部分如下所示:
2 0 LOAD_CONST 1 (300)
2 STORE_FAST 0 (a)
3 4 LOAD_CONST 1 (300)
6 STORE_FAST 1 (b)
...
如您所見,CPython 使用相同的常量槽加載a
和b
。 這意味着a
和b
現在引用同一個對象(因為它們引用同一個插槽),這就是為什么a is b
在腳本中為True
但在 REPL 中不是。
如果您將語句包裝在一個函數中,您也可以在 REPL 中看到這種行為:
>>> import dis
>>> def foo():
... a = 300
... b = 300
... print(a==b)
... print(a is b)
... print("id(a) = %d, id(b) = %d" % (id(a), id(b)))
...
>>> foo()
True
True
id(a) = 4369383056, id(b) = 4369383056
>>> dis.disassemble(foo.__code__)
2 0 LOAD_CONST 1 (300)
2 STORE_FAST 0 (a)
3 4 LOAD_CONST 1 (300)
6 STORE_FAST 1 (b)
# snipped...
底線:雖然 CPython 有時會進行這些優化,但您不應該真正指望它——它實際上是一個實現細節,並且隨着時間的推移它們已經改變(CPython 過去只對不超過 100 的整數執行此操作,因為例子)。 如果您要比較數字,請使用==
。 :-)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.