为什么`is`运算符在脚本中的行为与REPL不同?

时间:2019-03-25 23:00:15

标签: python cpython

在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

“是”运算符的结果不同。

2 个答案:

答案 0 :(得分:6)

.py脚本中运行代码时,整个文件compiled插入到执行 的代码对象中。在这种情况下,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中查找“ consts table”。

相反,当您直接在REPL中逐行运行代码时,它将在不同的上下文中执行。每行以“单”模式编译,因此无法进行此优化。

>>> scope = {} 
>>> lines = source.splitlines()
>>> for line in lines: 
...     code_obj = compile(line, filename="<I'm in the REPL, yo!>", 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)

答案 1 :(得分:4)

在这里,实际上有两件事要了解CPython及其行为。 首先,在内部[-5, 256]范围内的小整数被内在。 因此,即使在REPL处,该范围内的任何值都将共享相同的ID:

>>> 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 bTrue的原因,但是 不是在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)。如果要比较数字,请使用==。 :-)