可迭代对象拆包和切片赋值

5
L = [0, 1, 2]
L[::2], *rest = "abcdef"
print(L, rest)

期望输出:

['a', 1, 'b'] ['c', 'd', 'e', 'f']

实际输出:
ValueError: attempt to assign sequence of size 1 to extended slice of size 2

使用解包赋值与扩展切片赋值结合使用有什么原因不能实现吗?为什么?在PEP 3132 -- 扩展可迭代解包或Python数据模型中,我没有看到任何明显的理由表明这不应该是有效的。

1
这个问题即使没有 PEP 3132 也存在,例如 a, L [:: 2] ="abc" 不起作用 (a, L [:: 2] =[1,[2,3]] 起作用,但没多大意义)。 这看起来像是一个错误。 - thebjorn
1个回答

6
这不是一个bug。当左侧表达式有多个时,始终通过将右侧迭代对象中的每个项目映射到相应的逗号分隔左侧表达式来执行解包操作。在您的示例中,L[::2] 表达式只是两个左侧表达式之一,因此它仅从右侧迭代对象中接收一个项目,而星号左侧表达式接收其余部分。
正如 PEP-3132 中所指出的那样:

Many algorithms require splitting a sequence in a "first, rest" pair. With the new syntax,

first, rest = seq[0], seq[1:]

is replaced by the cleaner and probably more efficient:

first, *rest = seq

your:

L[::2], *rest = "abcdef"

因此,它等同于:
L[::2], rest = "a", "bcdef"

这会导致错误ValueError: attempt to assign sequence of size 1 to extended slice of size 2,因为"a"不能被拆分成长度为2的序列。
如果Python将您对解包的解释添加到语法中,则可以使以下语句更易读:
L[::2], *rest = "ab", "c", "d"

这段代码存在二义性—— L 应该变成 ["a", 1, "b"]rest 变成 ["c", "d"],还是应该将 L 变成 ["ab", 1, "c"],同时rest 变成 ["d"]?为了避免运行时错误,每个 LHS 表达式总是分配一个可迭代项,这样解释就更清晰、更不容易出错。


听起来很合理,这也解释了为什么 L = [1,2,3,4]; L[1:3], *r = "abcd" 的工作方式是这样的。但它仍然令人惊讶。 - thebjorn
我知道它为什么会被提起,但我的问题更多是关于是否可能实现得更加灵活。当LHS上的赋值钩入__getitem__并且想要消耗多个元素时,Python应该能够找到“一个明显的方法”来解决这个问题,不是吗? - wim
@wim 是的,但在我看来,您的解释并不一定是显而易见的方式,因为在我看来,如果按照您的建议,L[::2], *rest = (1, 2), 3, 4 可能会变得模棱两可-- L 应该变成 [1, 1, 2]rest 变成 [3, 4],还是 L 应该变成 [(1, 2), 1, 3]rest 变成 [4]?每个 LHS 表达式分配一个可迭代项可以使解释更清晰,减少运行时错误的可能性。 - blhsing
@blhsing 嗯,是的,我想到了这种潜在的歧义 - 也许你可以将这些细节编辑到你的答案中?(顺便说一句,我认为[(1,2),1,3][4]的结果显然更合理) - wim
要获得第一个结果[1, 1, 2][3, 4],需要进行类似以下的赋值:(L [:: 2],),*rest = (1, 2), 3, 4 - wim
@wim 我已经更新了我的答案,并提供了一个更好的示例,以演示您的建议可能会产生的潜在歧义。 - blhsing

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