从 python 生成器接收“返回”值的最佳方式

问题描述 投票:0回答:6

从 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 循环更简单的方法?

python generator
6个回答
59
投票

您可以将

value
StopIteration
属性(也可以说是
StopIteration
本身)视为实现细节,而不是设计用于“正常”代码中。

看看PEP 380,它指定了Python 3.3的

yield from
功能:它讨论了使用
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

18
投票

处理返回值的一种轻量级方法(不涉及实例化辅助类)是使用依赖注入

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

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

17
投票

您可以制作一个辅助包装器,它将捕获

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)

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

1
投票

有时合适的另一种轻量级方法是除了元组中的主要值之外,还生成每个生成器步骤中的运行摘要。循环保持简单,有一个额外的绑定,之后仍然可用:

for i, summary in produce_values():
    do_something(i)

show_summary(summary)

如果有人可以使用不仅仅是最后一个汇总值(例如),这尤其有用。 G。更新进度视图。


0
投票

现在具有完整的类型注释:

import typing
T = typing.TypeVar("T")
U = typing.TypeVar("U")
V = typing.TypeVar("V")


class ValuedGenerator(typing.Generic[T, U, V]):
    value: V | None = None

    def __init__(self, generator: typing.Generator[T, U, V]):
        self.generator = generator
        super().__init__()

    def __iter__(self) -> typing.Generator[T, U, None]:
        self.value = yield from self.generator

© www.soinside.com 2019 - 2024. All rights reserved.