昨天我偶然发现了Python2和Python3之间的这种奇怪的拆解差异,在谷歌上快速搜索了一下,似乎没有找到任何解释。
Python 2.7.8
a = 257
b = 257
a is b # False
a, b = 257, 257
a is b # False
Python 3.4.2
a = 257
b = 257
a is b # False
a, b = 257, 257
a is b # True
我知道这可能不会影响程序的正确性,但它确实让我感到有点困扰。有没有人能就拆包过程中的这种不同给出一些见解?
发布于 2014-12-12 22:26:46
这种行为至少在一定程度上与解释器如何进行常量折叠以及REPL如何执行代码有关。
首先,请记住CPython首先编译代码(编译为AST,然后是字节码)。然后,它计算字节码。在编译期间,脚本查找不可变的对象并缓存它们。它还可以对它们进行重复数据消除。所以如果它看到
a = 257
b = 257
它将针对同一对象存储a和b:
import dis
def f():
a = 257
b = 257
dis.dis(f)
#>>> 4 0 LOAD_CONST 1 (257)
#>>> 3 STORE_FAST 0 (a)
#>>>
#>>> 5 6 LOAD_CONST 1 (257)
#>>> 9 STORE_FAST 1 (b)
#>>> 12 LOAD_CONST 0 (None)
#>>> 15 RETURN_VALUE
请注意LOAD_CONST 1
。1
是co_consts
的索引
f.__code__.co_consts
#>>> (None, 257)
所以这两个都加载了相同的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; b = 257
>>> a is b
True
事实上,这两个Python版本都是这种情况。事实上,这就是为什么
>>> a, b = 257, 257
>>> a is b
True
也返回True
;这并不是因为解包的任何属性;它们只是被放在同一个编译单元中。
对于不能正确折叠的版本,这将返回False
;在2.7.3和3.2.3上显示此错误的filmor links to Ideone。在这些版本中,创建的元组不与其他常量共享其项:
import dis
def f():
a, b = 257, 257
print(a is b)
print(f.__code__.co_consts)
#>>> (None, 257, (257, 257))
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))
#>>> (148384292, 148384304, 148384496)
尽管如此,这并不是关于对象解包方式的更改;而只是对象在co_consts
中存储方式的更改。
发布于 2014-12-12 21:27:34
我认为这实际上是偶然的,因为我不能在Python3.2中重现这种行为。
有一个问题,http://bugs.python.org/issue11244引入了一个CONST_STACK
来修复负数的常量元组没有被优化的问题(看看针对peephole.c
的补丁,它包含了Python的优化器运行)。
这似乎也导致了给定的行为。仍在研究此问题:)
https://stackoverflow.com/questions/27443857
复制相似问题