【问题标题】:Implement coroutine in terms of async def根据 async def 实现协程
【发布时间】:2018-05-02 07:30:26
【问题描述】:

下面的例子定义了一个协程multiply,它等待一个值,将它乘以一个常数因子,然后打印结果。另一个函数product_table 使用multiply 生成产品表。

def multiply(factor):
    print(f"product table for {factor}")
    while True:
        sent_value = yield
        result = factor * sent_value
        print(f"{factor} x {sent_value} = {result}")

def product_table(coro):
    coro.send(None) # start coroutine
    for value in range(1, 11):
        coro.send(value)

product_table(multiply(3))

运行示例产生:

product table for 3
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15
3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
3 x 10 = 30

我试图在async defawait 方面实现完全相同的示例,但我无处可去。我最初的错误期望是,使用await 的以下协程函数等同于依赖yield 的协程:

async def multiply(factor):
    print(f"product table for {factor}")
    while True:
        await sent_value
        result = factor * sent_value
        print(f"{factor} x {sent_value} = {result}")

我认为这可能听起来很愚蠢,但对于我迟钝的大脑来说,它显示为“等待发送的值”。事实并非如此——我得到NameError: name 'sent_value' is not defined

到目前为止,我有限的理解是您可以根据yield(如第一个工作示例),根据async defawait定义协程函数,正如我在第二个示例中所尝试的那样。好像不是这样的。

我的具体问题是:

  1. 根据async def,您将如何实现第一个示例?
  2. async def 来实现是否有意义?

我讨厌我发现的所有怪异示例都是在 fake 管道方面到处使用time.sleep(0.1)。我正在尝试撰写一篇博客文章,其中包含使用更具体(如果也是微不足道的)管道的示例。

[删除错误编辑]

【问题讨论】:

  • async def 最初是作为语法糖,是的,是为了更容易发现你有一个协程(因为 def functionname() 不会传达身体某处有 yield 或 @ 987654342@)。我把它放在 cmets 中是因为你的那部分问题与你的实际问题相切,并且使你的问题边界过于宽泛。我会把它拿出来。
  • 您在编辑中实现的内容不起作用:TypeError: object range can't be used in 'await' expressionrange() 对象不是可等待的。
  • 射击 --- 我运行了错误的文件...正在编辑...
  • 具体示例:构建一个 Web 服务,将来自不同来源的信息聚合起来,然后将它们排列在一个列表中。假设你有一堆 REST API 要调用,一些数据库,一些文件数据要处理,然后聚合所有这些并在这些上运行一些排名,最后对结果进行排序。这是可等待对象的绝佳用例,因为您可以等待数据并行出现。这基本上就是 Facebook 和 Instagram 新闻提要的工作方式..
  • 请参阅 github.com/Instagram/django-workload/blob/master/… 了解此类设置的超级简化实现。

标签: python async-await python-asyncio coroutine


【解决方案1】:

awaityield from 相同,而不是yield;它将生成器的控制权委托给下一个

你没有委托控制,所以你不会使用async defawait;您只是将数据发送到另一个生成器。如果正在发送异常(使用generator.throw()),则不会将其传递给您的multiply() 生成器。

async def 的重点是创建 awaitable 协程,而await 仅适用于 awaitables。目标是您可以将这些链接在一起,并且仍然有效地将信息返回到驱动它们的事件循环。 yield 在可等待的协程中不使用(甚至不合法)。

您可能想阅读 Python 核心开发人员 Brett Cannon 撰写的这篇出色的博客文章,其中介绍了 async defawaityield from@asyncio.coroutine 如何结合在一起:How the heck does async/await work in Python 3.5?

可以将生成器与协程结合起来;从 Python 3.6 开始(实现了PEP 525 - Asynchronous Generators:

async def multiply(factor):
    print(f"product table for {factor}")
    while True:
        sent_value = yield
        result = factor * sent_value
        print(f"{factor} x {sent_value} = {result}")

请注意,我没有在这里替换 yield。你可以这样使用它:

async def product_table(coro):
    await coro.send(None) # start coroutine
    for value in range(1, 11):
        await coro.send(value)

使它成为协程没有什么意义,您没有在product_table() 生成器中使用任何等待对象。但是假设您想将此数据发送到网络套接字:

async def multiply(factor):
    await socket.send(f"product table for {factor}")
    while True:
        sent_value = yield
        result = factor * sent_value
        await socket.send(f"{factor} x {sent_value} = {result}")

现在您有了一个事件循环可以为您管理的东西,并行运行多个此类任务,在多个客户端准备好接收数据时将乘法表发送到它们。

【讨论】:

  • 我的想法是颠倒的。因此,协程可以接收生成器(可能是异步的,也可能不是异步的)并在每次“迭代”时委托给它。种类?
  • @Escualo:有点。 awaityield from 一样工作,但是有一个额外的限制,你必须有一个可等待的对象;这是为了防止您意外混合老式生成器和协程。
  • @Escualo:(这也为 Python 提供了优化协程堆叠在一起的空间)。
【解决方案2】:

用 async def 来实现是否有意义?

不,我认为不是。

碰巧asyncio 模块是使用生成器实现的,但理论上,它完全可以在没有生成器的情况下实现。尝试将 asyncio 用于生成器特定的工作是没有意义的。想象一下 asyncio 协程和生成器协程 - 是不同的东西。

粗略地说,您应该仅在需要并行执行一些 I/O 操作(例如下载多个 url)时才使用asyncio,仅此而已。 More about it.

【讨论】:

    【解决方案3】:

    当您使用async def 时,您正在创建一种新类型的一流对象,即协程对象。这些必须由异步事件循环驱动。 await 需要调用一个协程,它可能会被挂起。这些通常用于 IO。

    David Beazley 为此编写了一个不错的框架,称为curio。你可以看到一个基本的example echo server

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-06
      • 2017-03-27
      • 1970-01-01
      • 1970-01-01
      • 2017-11-04
      • 1970-01-01
      • 2021-01-17
      相关资源
      最近更新 更多