从 python 生成器接收 'return' 值的最佳方式
Best way to receive the 'return' value from a python generator
自 Python 3.3 起,如果生成器函数 return 是一个值,则该值将成为引发的 StopIteration 异常的值。这可以通过多种方式收集:
yield from
表达式的值,这意味着封闭函数也是一个生成器。
- 在 try/except 块中包装对
next()
或 .send()
的调用。
但是,如果我只是想在 for 循环中迭代生成器——最简单的方法——似乎没有办法收集 StopIteration 异常的值,因此 return 值。我使用了一个简单的示例,其中生成器生成值,并且 return 最后是某种摘要(运行 总计、平均值、时间统计等)。
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:
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 的回答是最简单的。
您可以将 StopIteration
的 value
属性(可以说是 StopIteration
本身)视为实现细节,而不是设计用于 "normal" 代码。
看看 PEP 380 指定 Python 的 yield from
特性 3.3:它讨论了一些使用 StopIteration
携带 return 考虑的价值。
由于您不应该在普通 for
循环中获取 return 值,因此没有语法。与您不应该明确捕获 StopIteration
的方式相同。
针对您的情况的一个很好的解决方案是一个小实用程序 class(对于标准库可能足够有用):
class Generator:
def __init__(self, gen):
self.gen = gen
def __iter__(self):
self.value = yield from self.gen
这会包装任何生成器并捕获其 return 值以供稍后检查:
>>> def test():
... yield 1
... return 2
...
>>> gen = Generator(test())
>>> for i in gen:
... print(i)
...
1
>>> print(gen.value)
2
我能想到的最明显的方法是用户定义的类型,它会为您记住摘要..
>>> import random
>>> class ValueProducer:
... def produce_values(self, n):
... self._total = 0
... for i in range(n):
... r = random.randrange(n*100)
... self._total += r
... yield r
... self.value_summary = self._total/n
... return self.value_summary
...
>>> v = ValueProducer()
>>> for i in v.produce_values(3):
... print(i)
...
25
55
179
>>> print(v.value_summary)
86.33333333333333
>>>
您可以制作一个辅助包装器,它会捕获 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)
处理 return 值的 light-weight 方法(不涉及实例化辅助 class 的方法)是使用 dependency injection.
也就是说,可以使用以下包装器/辅助生成器函数传入函数来处理/作用于 return 值:
def handle_return(generator, func):
returned = yield from generator
func(returned)
例如下面--
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)
结果--
1
2
returned: 3
自 Python 3.3 起,如果生成器函数 return 是一个值,则该值将成为引发的 StopIteration 异常的值。这可以通过多种方式收集:
yield from
表达式的值,这意味着封闭函数也是一个生成器。- 在 try/except 块中包装对
next()
或.send()
的调用。
但是,如果我只是想在 for 循环中迭代生成器——最简单的方法——似乎没有办法收集 StopIteration 异常的值,因此 return 值。我使用了一个简单的示例,其中生成器生成值,并且 return 最后是某种摘要(运行 总计、平均值、时间统计等)。
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:
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 的回答是最简单的。
您可以将 StopIteration
的 value
属性(可以说是 StopIteration
本身)视为实现细节,而不是设计用于 "normal" 代码。
看看 PEP 380 指定 Python 的 yield from
特性 3.3:它讨论了一些使用 StopIteration
携带 return 考虑的价值。
由于您不应该在普通 for
循环中获取 return 值,因此没有语法。与您不应该明确捕获 StopIteration
的方式相同。
针对您的情况的一个很好的解决方案是一个小实用程序 class(对于标准库可能足够有用):
class Generator:
def __init__(self, gen):
self.gen = gen
def __iter__(self):
self.value = yield from self.gen
这会包装任何生成器并捕获其 return 值以供稍后检查:
>>> def test():
... yield 1
... return 2
...
>>> gen = Generator(test())
>>> for i in gen:
... print(i)
...
1
>>> print(gen.value)
2
我能想到的最明显的方法是用户定义的类型,它会为您记住摘要..
>>> import random
>>> class ValueProducer:
... def produce_values(self, n):
... self._total = 0
... for i in range(n):
... r = random.randrange(n*100)
... self._total += r
... yield r
... self.value_summary = self._total/n
... return self.value_summary
...
>>> v = ValueProducer()
>>> for i in v.produce_values(3):
... print(i)
...
25
55
179
>>> print(v.value_summary)
86.33333333333333
>>>
您可以制作一个辅助包装器,它会捕获 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)
处理 return 值的 light-weight 方法(不涉及实例化辅助 class 的方法)是使用 dependency injection.
也就是说,可以使用以下包装器/辅助生成器函数传入函数来处理/作用于 return 值:
def handle_return(generator, func):
returned = yield from generator
func(returned)
例如下面--
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)
结果--
1
2
returned: 3