这种行为至少部分是由解释器如何进行常量折叠以及REPL执行代码的方式所决定的。
首先,请记住CPython首先将代码编译(到AST,然后到字节码)。然后再评估字节码。在编译期间,脚本查找不可变对象并缓存它们。它还对它们进行了去重。因此,如果它看到:
a = 257
b = 257
它将a和b存储在同一个对象中:
import dis
def f():
a = 257
b = 257
dis.dis(f)
请注意
LOAD_CONST 1
。其中的
1
是对
co_consts
的索引:
f.__code__.co_consts
所以这两个都加载了相同的
257
。为什么不会出现以下情况:
$ python2
Python 2.7.8 (default, Sep 24 2014, 18:26:21)
>>> a = 257
>>> b = 257
>>> a is b
False
$ python3
Python 3.4.2 (default, Oct 8 2014, 13:44:52)
>>> a = 257
>>> b = 257
>>> a is b
False
在这种情况下,每行代码都是一个单独的编译单元,去重不能跨越它们。它的工作方式类似于
compile a = 257
run a = 257
compile b = 257
run b = 257
compile a is b
run a is b
因此,这些代码对象都将具有独特的常量缓存。
这意味着如果我们去掉换行符,
is
将返回
True
:
>>> a = 257
>>> a is b
True
实际上,这适用于Python的各个版本。事实上,这正是为什么
>>> a, b = 257, 257
>>> a is b
True
这也会返回True
,并不是因为解包的任何属性;它们只是被放置在同一编译单元中。
对于不能正确折叠的版本,这将返回False
;filmor链接到Ideone,显示在2.7.3和3.2.3上失败。在这些版本中,创建的元组不与其他常量共享其项:
import dis
def f():
a, b = 257, 257
print(a is b)
print(f.__code__.co_consts)
n = f.__code__.co_consts[1]
n1 = f.__code__.co_consts[2][0]
n2 = f.__code__.co_consts[2][1]
print(id(n), id(n1), id(n2))
但需要说明的是,这并不是关于如何解包对象的更改;它仅仅是关于将对象存储在co_consts
中的更改。