【问题标题】:Best way to receive the 'return' value from a python generator从 python 生成器接收“返回”值的最佳方法
【发布时间】: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__ 设为生成器函数只会让您的迭代器返回无穷无尽的生成器流。)

标签: python generator


【解决方案1】:

您可以将StopIteration(也可以说是StopIteration 本身)的value 属性视为实现细节,而不是设计用于“普通”代码。

查看指定 Python 3.3 的 yield from 功能的 PEP 380:它讨论了使用 StopIteration 携带返回值的一些替代方案。

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

对于您的情况,一个不错的解决方案是一个小型实用程序类(可能对标准库足够有用):

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

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

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

>>> def test():
...     yield 1
...     return 2
...
>>> gen = Generator(test())
>>> for i in gen:
...    print(i)
...
1
>>> print(gen.value)
2

【讨论】:

  • 我的一位好同事不久前提出了对 for 循环语法的简单而优雅的扩展,它可以携带这样的 StopIteration 值,但被拒绝了......
  • 如果能指定for i in test() as value获取value作为生成器的返回值就好了。
【解决方案2】:

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

【讨论】:

  • 如果我自己无法修改“produce_values”生成器,这可能是最好的答案。如果这在我的控制范围内,我可能会让函数成为遵循迭代器协议的类。
  • 你想出了一个与我类似的解决方案。但如果可以的话:我的方法更简单:)
  • @ChrisCogdon:我认为你不可能找到任何不是你写的produce_values 生成器。这不是人们常用的模式。
  • @FerdinandBeyer:是的。本着竞争的精神,我现在通过窃取您的想法并装饰它来编辑答案。
  • @user2357112 :很可能是真的?
【解决方案3】:

一种处理返回值的轻量级方法(不涉及实例化辅助类)是使用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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 好主意。此外,我可以通过实现__next__ 方法使对象本身成为迭代器,而不是方法生成器。
    猜你喜欢
    • 2022-11-27
    • 2022-01-20
    • 2013-08-16
    • 2020-05-02
    • 1970-01-01
    • 2021-01-16
    • 2022-08-02
    • 2011-05-18
    相关资源
    最近更新 更多