为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

FFIVE 2021-12-16 16:13:41
在python中,两个代码有不同的结果:a = 300b = 300print (a==b)print (a is b)      ## print Trueprint ("id(a) = %d, id(b) = %d"%(id(a), id(b))) ## They have same address但是在 shell 模式下(交互模式):>>> a = 300>>> b = 300>>> a is bFalse>>> id(a)4501364368>>> id(b)4501362224“is”运算符有不同的结果。
查看完整描述

2 回答

?
临摹微笑

TA贡献1982条经验 获得超2个赞

当您在.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+FPyCode_Optimize以及有关“consts 表”的任何信息。


相反,当您直接在 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)


查看完整回答
反对 回复 2021-12-16
?
人到中年有点甜

TA贡献1895条经验 获得超7个赞

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


>>> 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 的整数执行此操作,因为例子)。如果您要比较数字,请使用==. :-)


查看完整回答
反对 回复 2021-12-16
  • 2 回答
  • 0 关注
  • 207 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号