从Python 3.3开始,如果生成器函数返回一个值,那么它将成为引发的StopIteration异常的值.这可以通过多种方式收集:
yield from表达式的值,暗示封闭函数也是一个生成器.next()或.send()中.但是,如果我只是想在for循环中迭代生成器 - 最简单的方法 - 似乎没有办法收集StopIteration异常的值,从而收集返回值.我使用一个简单的例子,其中生成器产生值,并在最后返回某种摘要(运行总计,平均值,时间统计等).
for i in produce_values():
do_something(i)
values_summary = ....??
Run Code Online (Sandbox Code Playgroud)
一种方法是自己处理循环:
values_iter = produce_values()
try:
while True:
i = next(values_iter)
do_something(i)
except StopIteration as e:
values_summary = e.value
Run Code Online (Sandbox Code Playgroud)
但这会抛弃for循环的简单性.我无法使用,yield from因为这需要调用代码本身就是一个生成器.有没有比上面显示的roll-one-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
Run Code Online (Sandbox Code Playgroud)
如果我不能重构价值生产者,@ Ferdinand Beyer的答案是最简单的.
Fer*_*yer 12
您可以value将StopIteration(并且可以说是StopIteration本身)的属性视为实现细节,而不是设计用于"普通"代码.
看看PEP 380,它指定了yield fromPython 3.3 的特性:它讨论了一些StopIteration用于携带返回值的替代方案.
由于您不应该在普通for循环中获取返回值,因此没有语法.你不应该StopIteration明确地捕捉到同样的方式.
对你的情况一个很好的解决方案是一个小的实用程序类(可能对标准库有用):
class Generator:
def __init__(self, gen):
self.gen = gen
def __iter__(self):
self.value = yield from self.gen
Run Code Online (Sandbox Code Playgroud)
这将包装任何生成器并捕获其返回值以便稍后检查:
>>> def test():
... yield 1
... return 2
...
>>> gen = Generator(test())
>>> for i in gen:
... print(i)
...
1
>>> print(gen.value)
2
Run Code Online (Sandbox Code Playgroud)
KT.*_*KT. 10
您可以创建一个帮助程序包装器,它将捕获StopIteration并为您提取值:
from functools import wraps
class ValueKeepingGenerator(object):
def __init__(self, g):
self.g = g
self.value = None
def __iter__(self):
self.value = yield from self.g
def keep_value(f):
@wraps(f)
def g(*args, **kwargs):
return ValueKeepingGenerator(f(*args, **kwargs))
return g
@keep_value
def f():
yield 1
yield 2
return "Hi"
v = f()
for x in v:
print(x)
print(v.value)
Run Code Online (Sandbox Code Playgroud)
一种处理返回值的轻量级方法(不涉及实例化辅助类的方法)是使用依赖注入。
即,可以使用以下包装器/帮助器生成器函数传入函数来处理/作用于返回值:
def handle_return(generator, func):
returned = yield from generator
func(returned)
Run Code Online (Sandbox Code Playgroud)
例如,以下——
def generate():
yield 1
yield 2
return 3
def show_return(value):
print('returned: {}'.format(value))
for x in handle_return(generate(), show_return):
print(x)
Run Code Online (Sandbox Code Playgroud)
结果是 -
1
2
returned: 3
Run Code Online (Sandbox Code Playgroud)