python 中的“yield”关键字是如何真正起作用的,尤其是当它带有递归时?

Posted

技术标签:

【中文标题】python 中的“yield”关键字是如何真正起作用的,尤其是当它带有递归时?【英文标题】:How does the 'yield' keyword in python really work, especially when it comes with recursion? 【发布时间】:2019-12-07 11:40:30 【问题描述】:

我正在使用 python 来展平嵌套列表,例如[1,2,[3,4,[5,[[6,7]]]]],我想创建一个生成器,以便我可以使用 for 循环在嵌套列表中一一打印所有数字。但它并没有像我预期的那样工作。

当我将 'yield' 关键字替换为 'print' 时,数字会一一打印出来。但是这样一来,它就不再是生成器了。

以下无法正常工作:

from collections.abc import Iterable

def flatten(nested):
    for item in nested:
        if isinstance(item, Iterable):
            flatten(item)
        else:
            yield item
x = [1,2,[3,4,[5,[[6,7]]]]]
y = flatten(x)

for element in y:
    print(element)

但是,如果我编写如下代码,我将 yield 替换为 print,数字会正确打印出来

from collections.abc import Iterable

def flatten(nested):
    for item in nested:
        if isinstance(item, Iterable):
            flatten(item)
        else:
            print(item)
x = [1,2,[3,4,[5,[[6,7]]]]]
y = flatten(x)

如果我写了'yield':

x = [[1,2],[3,4,[5,[[6,7]]]]]
y = flatten(x)
y.__next__()

错误消息将是y.__next__() StopIteration

【问题讨论】:

【参考方案1】:

这会修复您的代码:

from collections.abc import Iterable


def flatten(nested):
    for item in nested:
        if isinstance(item, Iterable):
            yield from flatten(item)
        else:
            yield item


x = [1, 2, [3, 4, [5, [[6, 7]]]]]
y = flatten(x)

for element in y:
    print(element)

这是因为你再次调用flatten,但忘记了yield from(毕竟新调用也返回了一个生成器)

请注意,isinstance(item, Iterable) 可能不是您想要的,因为它会因字符串而中断。字符串是Iterable,但在for 循环中,从它返回的字符本身就是字符串。可能最好检查它是否是一个列表。

def flatten(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten(item)
        else:
            yield item


for element in flatten([1, 2, ['three', 4, [5, [[6, 7]]]]]):
    print(element)

【讨论】:

你刚刚提醒了我python中的一个棘手点:字符是刺!因此,如果我在列表中包含一个字符串,它会将其展平为字符,但是字符仍然是刺痛的,因此它会不断变平......那是一场灾难。感谢您注意到我!【参考方案2】:

您永远不会从递归调用中返回或让步。添加yield from,它应该可以工作。

from collections.abc import Iterable

def flatten(nested):
    for item in nested:
        if isinstance(item, Iterable):
            yield from flatten(item) #change here.
        else:
            yield item
x = [1,2,[3,4,[5,[[6,7]]]]]
y = flatten(x)

for element in y:
    print(element)
#Output:
1
2
3
4
5
6
7

请注意,这个问题也存在于您的原始函数中,无论您使用的是 yield 还是 return。这就是为什么在仅使用print 并且在递归调用中没有返回时应该小心的原因,它可以掩盖这样一个事实,即虽然代码运行正常,但输出没有被正确捕获/使用。

【讨论】:

以上是关于python 中的“yield”关键字是如何真正起作用的,尤其是当它带有递归时?的主要内容,如果未能解决你的问题,请参考以下文章

python函数—yield的表达式形式

python的关键字yield有啥作用

yield的作用理解

Python三大器之生成器

理解Python协程:从yield/send到yield from再到async/await

理解Python协程:从yield/send到yield from再到async/await