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

How does the 'yield' keyword in python really work, especially when it comes with recursion?

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

当我用 'print' 替换 'yield' 关键字时,数字会一个接一个地打印出来。但是这样它就不再是发电机了。

以下无法正常工作:

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

您永远不会 return 从递归调用中退出或退出。添加一个 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 而没有使用 return 时应该小心,它可以掩盖这样一个事实,即虽然代码运行正常,但输出不正确 captured/used。

这修复了您的代码:

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(毕竟,新调用也是 returns 生成器)

请注意,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)