Python2和Python3中的解包行为有什么变化?

18

昨天我发现了Python 2和Python 3之间的一个奇怪的解包差异,在快速搜索了谷歌后似乎没有找到任何解释。

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

我知道这可能不影响程序的正确性,但这让我有点不舒服。是否有人能够解释一下unpacking中的这种差异呢?


4
没关系,你不应该依赖于任何实习行为... 应该使用比较。 - Karoly Horvath
4
Python将数字与表达式合并,我会找到相应的代码(对于之前的一个问题已经有了:) - filmor
4
这是一个内部实现细节,请勿依赖它。 - ch3ka
3
这种现象实际上与拆包无关,而是与小整数的内部化有关。 - PM 2Ring
2
这与整数无关。Python 3.x 只会为表达式中重复的(不可变)字面量生成一个对象。尝试使用浮点数或字符串代替。有些字面量进一步被内部化,以便在多个表达式中使用相同的对象。浮点数永远不会被内部化,只有小字符串会被内部化。 - Dunes
显示剩余5条评论
2个回答

24

这种行为至少部分是由解释器如何进行常量折叠以及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,并不是因为解包的任何属性;它们只是被放置在同一编译单元中。

对于不能正确折叠的版本,这将返回Falsefilmor链接到Ideone,显示在2.7.3和3.2.3上失败。在这些版本中,创建的元组不与其他常量共享其项:

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中的更改。


哇,非常感谢你,Veedrac!我真的没有想到一个小小的问题会让我对Python解释器的工作原理有如此详细的了解。 - Tiensbakung

8

我认为这实际上是偶然的,因为我无法使用Python 3.2重现这种行为。

有一个问题http://bugs.python.org/issue11244引入了一个CONST_STACK来解决常数元组中负数未被优化的问题(查看针对peephole.c的补丁,其中包含Python的优化器运行)。

这似乎也导致了给定的行为。仍在研究中 :)


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Tiensbakung
2
我刚在Python 2.5、2.6、2.7、3.3和3.4中尝试了a,b = 257,257。它们每一个都在交互式解释器中报告a is b为True。你确定你的原始实验结果吗? - Ned Batchelder
也许IPython在这里做了一些花哨的事情?交互式和脚本之间的区别在于前者执行LOAD_CONST 257; LOAD_CONST 257,而在后者中则折叠为LOAD_CONST (257, 257) - filmor
1
另外,我把这两个作为提示:http://ideone.com/FZrgt4(3.2),http://ideone.com/jV64I6(2.7) - filmor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接