簡體   English   中英

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

[英]Best way to receive the 'return' value from a python generator

自 Python 3.3 起,如果生成器 function 返回一個值,該值將成為引發的 StopIteration 異常的值。 這可以通過多種方式收集:

  • yield from表達式的值,這意味着封閉的 function 也是一個生成器。
  • 在 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-ones-own for 循環更簡單的方法?

您可以將StopIterationvalue屬性(可以說是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

您可以制作一個輔助包裝器,它會捕獲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)

處理返回值的一種輕量級方法(不涉及實例化輔助類)是使用依賴注入

即,可以使用以下包裝器/幫助器生成器函數傳入函數以處理/作用於返回值:

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

我能想到的最明顯的方法是用戶定義的類型,它會為你記住摘要..

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

另一種有時合適的輕量級方法是在每個生成器步驟中生成運行摘要,以及元組中的主要值。 循環保持簡單,有一個額外的綁定,之后仍然可用:

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

show_summary(summary)

如果某人可以使用的不僅僅是最后一個匯總值,例如更新進度視圖,這將特別有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM