簡體   English   中英

為什么 `is` 運算符在腳本和 REPL 中的行為不同?

[英]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 足夠聰明地意識到,由於ab都引用文字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 使用相同的常量槽加載ab 這意味着ab現在引用同一個對象(因為它們引用同一個插槽),這就是為什么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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM