提问人:Bokyun Na 提问时间:3/26/2019 最后编辑:wimBokyun Na 更新时间:7/9/2022 访问量:897
为什么“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”运算符具有不同的结果。
答:
在脚本中运行代码时,整个文件在执行之前会编译为代码对象。在这种情况下,CPython 能够进行某些优化 - 例如对整数 300 重用相同的实例。.py
您还可以在 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
其中一些优化非常激进。您可以修改脚本行,将其更改为 ,CPython 仍将“折叠”到相同的常量中。如果您对此类实现细节感兴趣,请在 peephole.c
和 Ctrl+F 中查找有关“consts 表”的任何信息。b = 300
b = 150 + 150
b
PyCode_Optimize
相反,当您直接在 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)
评论
b=150+150
关于 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 正在加载并使用相同的常量槽。
这意味着 和 现在引用同一个对象(因为它们
引用相同的插槽),这就是为什么在脚本中但
不在 REPL。a
b
a
b
a is b
True
如果将语句包装在函数中,也可以在 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 以内的整数这样做)。如果要比较数字,请使用 .:-)==
评论
dis
__code__
评论
.py
>>>
is