Python 解包陷阱(意外行为)

Python unpacking gotcha (unexpected behavior)

谁能解释一下这是怎么回事?为什么会这样?

>>> b = "1984"
>>> a = b, c = "AB"
>>> print(a, b, c)
'AB', 'A', 'B'

这种行为真的让我大吃一惊。 找到这个 here

好酷的问题!带来很多乐趣! :) 可以在面试中使用 :)

好的,我们到了

>>> b = "1984"
>>> a = b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = (b, c) = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>>

在python中对于多重赋值,你可以省略(...)并且看起来python解析这一行类似于2行

a = "AB"
b, c = "AB" # which is equal to (b, c) = "AB"

更多示例

>>> a = b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = (b, c) = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>> a = "AB"
>>> b, c = "AB"
>>> print((a,b,c))
('AB', 'A', 'B')
>>>

它很好地使用列表:)

>>> a = [b, c] = 'AB'
>>> print((a,b,c))
('AB', 'A', 'B')
>>>

更多示例:

赋值是一个语句;它被定义为将最右侧分配给从左到右的各种目标。比较干 language grammar description is:

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

例如:

a = b = 1

1 分配给 a,然后再次将其分配给 b,与您所做的大致相同:

__specialtmp = 1
a = __specialtmp
b = __specialtmp

其中 __specialtmp 是一个未命名的临时存储位置(在 CPython 上,它只是加载到程序堆栈的顶部,然后复制为两个引用,然后每个引用被弹出以进行分配)。

这只是将可迭代解包添加到组合中;以同样的方式扩展您的代码,它看起来像:

__specialtmp = "AB"
a = __specialtmp  # Assigns original value to a
b, c = __specialtmp  # Unpacks string as iterable of its characters, assigning "A" to b, and "B" to c

请注意,这并不总是有效;如果被解包的东西是一个迭代器,并且你分配给解包的名字 first,迭代器将被耗尽并且没有任何有用的东西可用于第二次赋值:

b, c = [*a] = iter("AB")

这会将 "A" 解压缩为 b,将 "B" 解压缩为 c,但是当它到达 a 时,即 [*a] = iter("AB") ] 将变为 ["A", "B"](星形语法将 "remaining" 值捕获到 list),在这种情况下,迭代器会耗尽填充 bca 得到 nothing(空 list[])。

要点是,虽然这个技巧有效,但我一般不会推荐它。将多个名称初始化为相同的不可变值是可以的,但否则它可能会咬你。

让我们把这个简单一点。我们来看下面的案例

>>> b = 1
>>> b, c = (0, 2)
>>> print(b, c)
0, 2

b 是 0 而不是 1 是不是很奇怪?不应该是因为我们在调用 b, c = (0, 2) 时将 b 分配给 0,将 c 分配给 2,这要归功于元组解包。

现在要解决陷阱的另一部分,让我们举这个例子

>>> b = 1
>>> a = b = 0
>>> print (b)
0

b 是 0 而不是 1 是不是又令人惊讶了?同样,这不应该是因为在调用 a = b = 0 时,我们已经通过多重赋值将 ab 都赋给了 0。

所以回到陷阱,a = b, c = "AB" 只是这两种行为的组合。 b, c = "AB" 会将 "A" 解包为 b,将 "B" 解包为 c,我们还将 "AB" 分配给 a。虽然看起来我们正在分配 a = b,但实际上我们只是在执行以下两行

>>> b = "1984"
>>> b, c = "AB"
>>> a = "AB"

希望这可以分解元组解包发生的位置和赋值发生的位置,并且它不像看起来那样令人困惑。