猿问

为什么`is` 运算符在脚本和 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”运算符有不同的结果。


FFIVE
浏览 208回答 2
2回答

临摹微笑

当您在.py脚本中运行代码时,整个文件在执行之前会被编译成一个代码对象。在这种情况下,CPython 能够进行某些优化——比如对整数 300 重用相同的实例。您还可以通过在更类似于脚本执行的上下文中执行代码来在 REPL 中重现它:>>> source = """\&nbsp;... a = 300&nbsp;... b = 300&nbsp;... print (a==b)&nbsp;... print (a is b)## print True&nbsp;... print ("id(a) = %d, id(b) = %d"%(id(a), id(b))) ## They have same address&nbsp;... """>>> code_obj = compile(source, filename="myscript.py", mode="exec")>>> exec(code_obj)&nbsp;TrueTrueid(a) = 140736953597776, id(b) = 140736953597776其中一些优化非常激进。您可以修改脚本行,b = 300将其更改为b = 150 + 150,CPython 仍会“折叠”b为相同的常量。如果您对此类实现细节感兴趣,请查看peephole.c和 Ctrl+FPyCode_Optimize以及有关“consts 表”的任何信息。相反,当您直接在 REPL 中逐行运行代码时,它会在不同的上下文中执行。每行都以“单一”模式编译,此优化不可用。>>> scope = {}&nbsp;>>> lines = source.splitlines()>>> for line in lines:&nbsp;...&nbsp; &nbsp; &nbsp;code_obj = compile(line, filename="<I'm in the REPL, yo!>", mode="single")...&nbsp; &nbsp; &nbsp;exec(code_obj, scope)&nbsp;...TrueFalseid(a) = 140737087176016, id(b) = 140737087176080>>> scope['a'], scope['b'](300, 300)>>> id(scope['a']), id(scope['b'])(140737087176016, 140737087176080)

人到中年有点甜

关于 CPython 及其行为,实际上有两件事需要了解。首先,[-5, 256]范围内的小整数在内部被内部存储。因此,即使在 REPL 中,落在该范围内的任何值都将共享相同的 id:>>> a = 100>>> b = 100>>> a is bTrue由于 300 > 256,它没有被实习:>>> a = 300>>> b = 300>>> a is bFalse其次,在脚本中,文字被放入编译代码的常量部分。Python 足够聪明,可以意识到,由于a和 都b 引用文字,300而且它300是一个不可变对象,因此它可以继续引用相同的常量位置。如果您稍微调整一下脚本并将其编写为:def foo():&nbsp; &nbsp; a = 300&nbsp; &nbsp; b = 300&nbsp; &nbsp; print(a==b)&nbsp; &nbsp; print(a is b)&nbsp; &nbsp; print("id(a) = %d, id(b) = %d" % (id(a), id(b)))import disdis.disassemble(foo.__code__)输出的开头部分如下所示:2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 LOAD_CONST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 (300)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2 STORE_FAST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 (a)3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4 LOAD_CONST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 (300)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 6 STORE_FAST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 (b)...如您所见,CPython 正在加载a和b使用相同的常量槽。这意味着,a和b现在指的是同一个对象(因为它们引用相同的插槽),这就是为什么a is b是True在脚本,但不是在REPL。如果将语句包装在函数中,您也可以在 REPL 中看到这种行为:>>> import dis>>> def foo():...&nbsp; &nbsp;a = 300...&nbsp; &nbsp;b = 300...&nbsp; &nbsp;print(a==b)...&nbsp; &nbsp;print(a is b)...&nbsp; &nbsp;print("id(a) = %d, id(b) = %d" % (id(a), id(b)))...>>> foo()TrueTrueid(a) = 4369383056, id(b) = 4369383056>>> dis.disassemble(foo.__code__)&nbsp; 2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 LOAD_CONST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 (300)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 2 STORE_FAST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 (a)&nbsp; 3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;4 LOAD_CONST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 (300)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 6 STORE_FAST&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 (b)# snipped...底线:虽然 CPython 有时会进行这些优化,但您真的不应该指望它——它确实是一个实现细节,并且随着时间的推移而发生了变化(CPython 过去只对最多 100 的整数执行此操作,因为例子)。如果您要比较数字,请使用==. :-)
随时随地看视频慕课网APP

相关分类

Python
我要回答