从Python2到Python3的解包行为的这种变化是什么?

Tie*_*ung 18 python python-2.7 python-3.x difference iterable-unpacking

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

Python 2.7.8

a = 257
b = 257
a is b # False

a, b = 257, 257
a is b # False
Run Code Online (Sandbox Code Playgroud)

Python 3.4.2

a = 257
b = 257
a is b # False

a, b = 257, 257
a is b # True
Run Code Online (Sandbox Code Playgroud)

我知道它可能不会影响程序的正确性,但它确实让我有点烦恼.任何人都可以在拆包时给出一些关于这种差异的见解吗?

Vee*_*rac 24

这种行为至少部分与解释器如何进行常量折叠以及REPL如何执行代码有关.

首先,请记住CPython首先编译代码(到AST然后是字节码).然后它评估字节码.在编译期间,脚本会查找不可变的对象并对其进行缓存.它还对它们进行重复数据删除.如果它看到了

a = 257
b = 257
Run Code Online (Sandbox Code Playgroud)

它会将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
Run Code Online (Sandbox Code Playgroud)

请注意LOAD_CONST 1.该1是索引co_consts:

f.__code__.co_consts
#>>> (None, 257)
Run Code Online (Sandbox Code Playgroud)

所以这些都加载相同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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,每一行都是一个单独的编译单元,重复数据删除不能跨越它们.它的工作原理类似于

compile a = 257
run     a = 257
compile b = 257
run     b = 257
compile a is b
run     a is b
Run Code Online (Sandbox Code Playgroud)

因此,这些代码对象都将具有唯一的常量缓存.这意味着如果我们删除换行符,is将返回True:

>>> a = 257; b = 257
>>> a is b
True
Run Code Online (Sandbox Code Playgroud)

事实上,这两个Python版本都是如此.事实上,这正是原因所在

>>> a, b = 257, 257
>>> a is b
True
Run Code Online (Sandbox Code Playgroud)

也回来True了; 这不是因为解包的任何属性; 它们只是放在同一个编译单元中.

这将返回False不正确折叠的版本; 电影或链接到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)
Run Code Online (Sandbox Code Playgroud)

但是,这不是关于如何解开对象的变化; 它只是对象存储方式的改变co_consts.


fil*_*mor 8

我认为这实际上是偶然的,因为我不能用Python 3.2重现这种行为.

这个问题http://bugs.python.org/issue11244引入了一个CONST_STACK修复常量元组的问题,其中负数未被优化(查看补丁peephole.c,其中包含Python的优化运行).

这似乎也导致了给定的行为.还在调查这个:)

  • 我只是在Pythons 2.5,2.6,2.7,3.3,3.4中尝试过`a,b = 257,257`.他们每个人都在交互式翻译中将'a is b`报告为True.你确定你原来的实验吗? (2认同)