从基于生成器的协程转换为本地协程

9

我已经使用Python很多年了,但只逐渐学习了语言中更为晦涩的特性,因为我的大部分代码都是用于数据处理的。基于yield的生成器是我日常工具包的一部分,最近我了解了协程。我发现一个类似于这样的示例:

def averager():
    sum = 0.0
    n = 0
    while True:
        value = yield
        sum += value
        n += 1
        print(sum/n)

avg = averager()
next(avg) # prime the coroutine
avg.send(3)
avg.send(4)
avg.send(5)

这个函数会打印出传递给它的值的平均值。我认为这样的函数在数据处理管道中可能会有用,所以我决定将其记在脑后。然而,在阅读了Python文档中的以下通知后,我改变了想法:

基于生成器的协程支持已被弃用,并计划在Python 3.10中删除。

显然,我想编写具有未来性的代码,因此现在开始学习基于生成器的协程可能已经没有意义了。那么,我的问题是:如何使用本机(asyncio)协程实现此示例? 我很难理解本机协程语法。

在寻找答案时,我发现一个相关问题,其中有一个评论和一个答案基本上是在说“你不能使用async,请改用基于yield的协程”。但如果这些被淘汰了,在3.10+中是否还有一种使用协程的方法?


1
@RomanPerekhrest:好吧,这就是我的问题,不是吗?如果你告诉我“你做不到”,那么这意味着使用协程来实现这样的事情将从Python中消失而没有任何替代方案,这将是不方便的。当然,还有其他方法可以实现相同的效果,所以这不是什么大问题,但仍然很不幸。 - Jsl
1
我认为弃用通知可能仅适用于asyncio.coroutine装饰器。Python 3.10文档中说:“_支持基于生成器的协程已被弃用,并在Python 3.11中删除_”。使用PEP 342中方便的消费者装饰器的基于生成器的协程仍可在Python 3.11中使用。 - Brecht Machiels
1个回答

5

现在,异步生成器来了……

因此,在异步上下文中,我们仍然拥有这种能力
至于理论方面——提到的PEP 525提供了很好的描述,绝对值得一读。
我将发布一个为异步averager准备的示例,其中包括初始化安全结束

import asyncio

async def coro():
    print('running other coroutine in 3 sec ...')
    await asyncio.sleep(3)  # emulate working


async def averager():
    sum_ = n = 0
    while True:
        v = yield
        sum_ += v
        n += 1
        print(sum_ / n)
        await asyncio.sleep(0.1)


async def main():
    agen = averager()
    await agen.asend(None)
    print(agen.__name__, 'initialized ...')

    await agen.asend(3)
    print('another separate processing here ...')

    await coro()

    await agen.asend(4)
    await agen.asend(14)


loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(main())
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()

程序输出:

averager initialized ...
3.0
another separate processing here ...
running other coroutine in 3 sec ...
3.5
7.0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接