Python中关于“解包”生成器的规则是什么?

3
这里是一个简单的有界生成器。
def bounded_naturals(limit):
    num = 1
    while num <= limit:
        yield num
        num += 1

如果我写:
bn_gen = bounded_naturals(3)

bn_gen将会是一个生成器对象,正如预期的一样。

但如果我写成:

(a, b, c) = bounded_naturals(3)

abc 的值将分别为 1、2 和 3。这让我感到奇怪,因为在代码中似乎没有要求生成器产生值的地方。Python 规范中是否有要求采用这种解释的地方呢?

更加引人注目的是,如果我写成:

bn_gen = (a, b, c) = bounded_naturals(3)

我同时得到了两种结果!bn_gen 将成为生成器对象,而 abc 则分别是 1、2 和 3。我应该怎样理解这是怎么回事呢?

最后,如果我写下:

(a, b) = bounded_naturals(3)

我遇到了一个问题:ValueError: too many values to unpack (expected 2).

如果编译器可以聪明地执行这些其他技巧,为什么它在这种情况下不够聪明,不仅要求生成器提供所需数量的元素?

Python文档中是否有解释所有这些的部分?

谢谢。


1
我可能对此有所错误,而且现在无法尝试,因为我正在使用移动设备,但你的实验似乎表明你可以解压缩所有内容或不解压缩任何内容。 - Pranav Hosangadi
(a, b) = bounded_naturals(3)在这个表达式中,你打算如何访问第三个元素? 注:该翻译假定“bounded_naturals”为专有名词,不予翻译。 - Rishabh Kumar
2个回答

5

解压操作作用于任意迭代器,而非序列,并通过迭代执行该操作。当你执行以下操作时

(a, b, c) = bounded_naturals(3)

您正在要求Python迭代bounded_naturals(3)并将结果分配给abc


像这样的多重赋值

bn_gen = (a, b, c) = bounded_naturals(3)

这段代码的执行顺序是从左到右逐一将右侧表达式赋值给对应的赋值目标(不像其他语言一样从右到左)。生成器首先被分配给bn_gen,然后再分配给(a, b, c)。请注意,解包操作会耗尽生成器,所以迭代bn_gen将得不到任何结果。


当你执行

(a, b) = bounded_naturals(3)

失败与Python不够聪明无关。Python不会默默地丢弃额外的值,因为那样只会隐藏错误。可迭代对象必须恰好提供与解包请求一样多的元素。

请记住,仅仅因为某些代码“可以”被赋予非错误的含义,并不意味着它“应该”这样做。


所有这些都在赋值语句文档中有记录。


值得一提的是,您分配的生成器将始终为空。 - Mad Physicist
我不希望“Python [to] silently discard extra values.” 更好的做法是,在=右侧的“object”是一个迭代器且左侧有多个目标时,应该有一个特殊规则。在这种情况下,应该要求迭代器提供与目标数量相同的项目。这不会“discard extra values.” 它将使用迭代器的本意。当一些元素被生成时,未生成的元素仍然可用于以后的生成。 - RussAbbott
@RussAbbott:然后你可以使用itertools.islice显式地请求它,但是如果Python默认采用您想要的行为,那将导致大量的静默丢弃值错误。认为他们已经完全解包了某些东西的人将远远超过实际受益于这种行为的人数。 - user2357112
此外,为了支持您所需的行为,Python 必须在生成最后一个请求的值后停止运行生成器,而不是一直运行到完成。这会导致问题(特别是在其他 Python 实现中,如 PyPy),当生成器需要执行清理操作时,例如如果它有 finally 块或 __exit__ 方法等待时。 - user2357112
@user2357112 支持 Monica,我不太明白你的推理。迭代器能够响应 next() 请求。如果 RHS 上的迭代器响应 next() 请求直到它用尽元素(在这种情况下,它通常会生成 StopIteration 错误)或用尽目标(我的情况),未完全释放的迭代器仍然可用于将来的 next() 请求。为什么这是个问题? - RussAbbott
@RussAbbott:因为人们会认为他们已经完全解压了他们没有的东西,而且如果你在第三个元素之后停止一个3元素生成器,而不是运行它直到返回,它将不会释放任何它所持有的资源。 - user2357112

1
您要查找的文档在这里
assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)
...
将对象分配给目标列表,可以选择地用括号或方括号括起来,递归地定义如下。
- 如果目标列表是单个目标且没有尾随逗号,则可选地在括号中,将对象分配给该目标。 - 否则:对象必须是具有与目标列表中目标数量相同的项数的可迭代对象,并且从左到右将项目分配给相应的目标。
我假设您知道生成器是可迭代的。让我们看看代码。
bn_gen = (a, b, c) = bounded_naturals(3)
这里有两个target_list:一个单个目标bn_gen和三个目标(a, b, c)。由于bn_gen是单个目标,无论bounded_naturals(3)返回什么都将被分配给它(在本例中为生成器本身)。然后(a, b, c)被视为具有多个目标的目标列表,RHS(右侧)被“解包”并逐个分配。

所以,正如@Pranav Hosangadi在评论中指出的那样,您只能解压缩所有内容或不解压缩任何内容。


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