【发布时间】:2016-03-08 12:03:15
【问题描述】:
从 Python 3.3 开始,如果生成器函数返回一个值,则该值将成为引发的 StopIteration 异常的值。这可以通过多种方式收集:
-
yield from表达式的值,这意味着封闭函数也是一个生成器。 - 将对
next()或.send()的调用封装在try/except 块中。
但是,如果我只是想在 for 循环中迭代生成器 - 最简单的方法 - 似乎没有办法收集 StopIteration 异常的值,从而收集返回值。我使用了一个简单的示例,其中生成器产生值,并在最后返回某种摘要(运行总计、平均值、时间统计等)。
for i in produce_values():
do_something(i)
values_summary = ....??
一种方法是自己处理循环:
values_iter = produce_values()
try:
while True:
i = next(values_iter)
do_something(i)
except StopIteration as e:
values_summary = e.value
但这抛弃了 for 循环的简单性。我不能使用yield from,因为这要求调用代码本身就是一个生成器。有没有比上面显示的roll-ones-own for 循环更简单的方法?
答案总结
结合@Chad S. 和@KT 的答案,最简单的方法似乎是使用迭代器协议将我的生成器函数变成一个类:
class ValueGenerator():
def __iter__(self):
yield 1
yield 2
# and so on
self.summary = {...}
vg = ValueGenerator()
for i in vg:
do_something(i)
values_summary = vg.summary
如果我不能重构价值生产者,@Ferdinand Beyer 的答案是最简单的。
【问题讨论】:
-
我不认为生成器返回值曾经打算在子生成器的上下文之外使用,将值返回到封闭的生成器,所以这很尴尬。一个明确的 try-except 可能是你最好的选择。 (另外,你想要
e.value,而不仅仅是e。) -
@user2357112:修复了编码错误:谢谢。我理解评论,但它似乎是一个非常有用的结构,我很惊讶它像那样尴尬。答案很可能是“For 循环适用于迭代器。如果您将生成器用于除简单迭代器之外的任何东西,那么 for 循环不适合您。”
-
我认为更多的是你试图给生成器太多的责任。调用者可以更合理地完成诸如摘要或时间信息之类的事情。如果你真的想做这样的事情,我可能会在生成器周围创建一个包装器,并为包装器提供一个
summary方法,以便在迭代完成后调用。 -
@user2357112 :只有在值中有足够的信息来形成这些摘要时,这才有效。这就是为什么我在我的人为示例中将“时间”添加到摘要信息中的原因:) 但是,如果我要达到那个详细程度,那么带有迭代器协议的类可能远更多合适。
-
(您的类转换错误;将
__next__设为生成器函数只会让您的迭代器返回无穷无尽的生成器流。)