使用递归时,Python 中的"yield"会上升调用堆栈吗?



当我在学习Bill Lubanovic的introduction Python时,我修改了一些代码来理解第九章中的flatten()函数。

def flatten(lol):
for item in lol:
if isinstance(item, list):
for subitem in flatten(item):
print('yield1 ', subitem)
yield subitem
else:
print('yield2 ', item)
yield item
lol = [1, 2, [3, 4, 5], [6, [7, 8, 9],[]]]
list(flatten(lol))

我期望的输出是

('yield2 ', 1)
('yield2 ', 2)
('yield2 ', 3)
('yield2 ', 4)
('yield2 ', 5)
('yield1 ', 3)
('yield1 ', 4)
('yield1 ', 5)
...
...(skipped)
但是程序的正确输出是这样的:
('yield2 ', 1)
('yield2 ', 2)
('yield2 ', 3)
('yield1 ', 3)
('yield2 ', 4)
('yield1 ', 4)
('yield2 ', 5)
('yield1 ', 5)
...
...(skipped)

我不明白为什么"('yield1 ', 3)"在('yield2 ', 4)之前打印,即使调用inner flatten()中的循环尚未结束。我想知道'yield'在递归时是否进行堆栈展开。

当你迭代for subitem in flatten(item)时,调用返回到flatten,它首先打印yield2 value,然后你在循环中使用yield1 value再次打印它,这就是为什么,它被打印两次,一次为yield1,一次为yield2,它不会像你在问题标题中提到的那样上升堆栈。

另一方面,建议使用yield from ...递归回调生成器,而不是手动迭代生成器调用。

def flatten(lol):
for item in lol:
if isinstance(item, list):
yield from flatten(item)
# for subitem in flatten(item):
#    print('yield1 ', subitem)
#     yield subitem
else:
print('yield2 ', item)
yield item
lol = [1, 2, [3, 4, 5], [6, [7, 8, 9], []]]
list(flatten(lol))

输出:

yield2  1
yield2  2
yield2  3
yield2  4
yield2  5
yield2  6
yield2  7
yield2  8
yield2  9
[1, 2, 3, 4, 5, 6, 7, 8, 9]

是的,yield语句挂起当前函数的执行,并将程序的控制权交给调用代码,其中产生的项将是生成器迭代时的下一个值。只有当从生成器请求另一个值时,函数的内部调用才会恢复。

如果你尝试一个较小的输入,并尝试用next():

手动迭代它,你可能会更好地理解事情。
>>> gen = flatten([[1, [2]], 3])
>>> print("output", next(gen))
yield2  1
yield1  1
output 1
>>> print("output", next(gen))
yield2  2
yield1  2
yield1  2
output 2
>>> print("output", next(gen))
yield2  3
output 3
>>> print("output", next(gen))
Traceback (most recent call last):
File "<ipython-input-16-1c675fe35f03>", line 1, in <module>
print("output", next(gen))
StopIteration