从python生成器接收'return'值的最佳方法

时间:2015-12-03 18:19:06

标签: python generator

从Python 3.3开始,如果生成器函数返回一个值,那么它将成为引发的StopIteration异常的值。这可以通过多种方式收集:

  • yield from表达式的值,暗示封闭函数也是生成器。
  • 在try / except块中包含对next().send()的来电。

但是,如果我只是想在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-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

如果我不能重构价值生产者,那么@Ferdinand Beyer的回答是最简单的。

4 个答案:

答案 0 :(得分:10)

您可以将value的{​​{1}}属性(可以说是StopIteration本身)视为实施细节,而非设计用于“普通”代码。

查看指定Python 3.3的StopIteration功能的PEP 380:它讨论了使用yield from来承载返回值的一些备选方案。

由于您不应该在普通的StopIteration循环中获取返回值,因此没有语法。与您不应该明确捕获for的方式相同。

适合您情况的一个很好的解决方案是一个小实用程序类(可能对标准库有用):

StopIteration

这包装了任何生成器并捕获其返回值以便稍后检查:

class Generator:
    def __init__(self, gen):
        self.gen = gen

    def __iter__(self):
        self.value = yield from self.gen

答案 1 :(得分:8)

你可以制作一个帮助包装器,它会捕获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)

答案 2 :(得分:4)

处理返回值的轻量级方法(不涉及实例化辅助类的方法)是使用dependency injection

即,可以使用以下包装器/辅助生成器函数传递函数来处理/操作返回值:

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

答案 3 :(得分:3)

我能想到的最明显的方法是用户定义的类型,它会为你记住摘要..

>>> 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
>>>