【问题标题】:How to understand `yield from` in python coroutine?如何理解 python 协程中的“yield from”?
【发布时间】:2021-11-26 17:04:11
【问题描述】:

代码来自Fluent Python 1st edtion

我无法理解grouper 中的while True: 行,删除该行会引发StopIteration 错误。

但我发现没有while True:grouper 的新版本有效。为什么group.send(None) 需要在while True:(或另一个results[key] = yield from averager())中使用另一个循环?

我的理解是group.send(None) 将停止yield from averager() 并为results[key] 分配一个值(Result(count, average))。就是这样。

from collections import namedtuple

Result = namedtuple('Result', 'count average')


# the subgenerator
def averager():  # <1>
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield  # <2>
        if term is None:  # <3>
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)  # <4>


# the delegating generator
def grouper(results, key):  # <5>
    while True:  # <6>
        results[key] = yield from averager()  # <7>

# Another version works
#def grouper(results, key):
#    results[key] = yield from averager()
#    results[key] = yield from averager()

# the client code, a.k.a. the caller
def main(data):  # <8>
    results = {}
    for key, values in data.items():
        group = grouper(results, key)  # <9>
        next(group)  # <10>
        for value in values:
            group.send(value)  # <11>
        group.send(None)  # important! <12>

    # print(results)  # uncomment to debug
    report(results)


# output report
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(
              result.count, group, result.average, unit))


data = {
    'girls;kg':
        [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],
    'girls;m':
        [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],
    'boys;kg':
        [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],
    'boys;m':
        [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],
}


if __name__ == '__main__':
    main(data)

【问题讨论】:

    标签: python coroutine yield-from


    【解决方案1】:

    这让我想起了 nice ascynio 是多么的好,以及为什么每个人都应该使用它...

    通过遍历迭代器的操作可以最好地解释正在发生的事情。这是内部生成器,简化:

    def averager():
        local_var
        while True:
            term = yield
            if term is None:
                break
            local_var = do_stuff(term)
        return local_var
    

    这做了两件事。首先,它使用yield 获取一些数据(呃,解释 的单词选择只是令人困惑)只要该数据不是None。然后当它None时,它会产生一个StopIterationException,其值为local_var。 (这就是从生成器返回的作用)。

    这是外部生成器:

    def grouper(results, key):
        while True:
            results[key] = yield from averager()
    

    this 所做的是将内部生成器的 yield 暴露给调用代码,直到内部生成器引发 StopIterationException,这被静默捕获(通过yield from 语句)并分配。然后它准备好再次做同样的事情。

    然后我们有调用代码:

    def main(data):
        results = {}
        for key, values in data.items():
            group = grouper(results, key)
            next(group)
            for value in values:
                group.send(value)
            group.send(None)
    

    这个的作用是:

    • 它只迭代外部生成器一次
    • 这会公开内部生成器的产量,并使用它 (.send) 与内部生成器进行通信。
    • 它通过发送None“结束”内部生成器,此时第一个yield from 语句结束,并分配向上传递的值。
    • 此时,外部生成器已准备好发送另一个值
    • 循环继续,生成器被垃圾回收删除。

    while True: 循环是怎么回事?

    考虑这段代码,它也适用于外部生成器:

    def grouper(result, key):
        result[key] = yield from averager
        yield 7
    

    唯一重要的是生成器不应耗尽,因此它不会将异常传递到链上,说“我没有什么可迭代的了”。

    附:使困惑?我曾是。我不得不检查一下,自从我尝试使用基于生成器的 coros 以来已经有一段时间了。它们已被安排删除——使用 asyncio,它要好得多

    【讨论】:

    • 谢谢你的详细解释,我终于知道StopIteration没有while True:来自耗尽的外部生成器,所以另一个yield被用来压制它。
    猜你喜欢
    • 2021-11-30
    • 2020-05-09
    • 2014-01-10
    • 2016-06-01
    • 2023-01-25
    • 2018-04-29
    • 2017-10-28
    • 2014-11-16
    • 2019-12-06
    相关资源
    最近更新 更多