[Python]前往Python Async的旅程-4。本地Coroutines
#网络开发人员 #python #网络

什么是本地的Coroutine?

从上一篇文章中,我们讨论了如何将发电机视为的coroutines,但是不能被完全接受为Coroutines,并且由于PEP 492,我们终于有了本机构python中的coroutine对象。

如果您阅读了PEP 492official docs about the coroutine objects,它说coroutine对象是

  • async… await…关键字构建
  • 基本上是在没有这样的迭代功能的发电机上构建的(没有__next__()__iter__()),但是与生成器共享接口(例如send()throw()close()
  • 配备了__await__(),因此可以使用await关键字(__await__()返回迭代器)

要看看这些事情到底是什么意思,我们会观察到一些简单的示例,这些例子证明了本地coroutines的行为。

,但让我们首先研究Coroutine的结构。

本地冠心的内部结构

实际上,它们不是全新的对象,而是具有与发电机的对象结构非常相似的对象结构,而发电机的核心方法send()shared。因此,我们可以期望在引擎盖下执行的Coroutine的实际逻辑与发电机的逻辑有些相似,而在某些方面暂停了执行状态。

从某种意义上说,这种期望是正确的,尽管它不一定是 - 一个本地的Coroutine根本不需要暂停,但可以。这主要是因为本机统治提供了程序员freedom to choose what to do with them-只要await表达式之后的对象是本机coroutines,或者是通过__await__()返回迭代器的对象。

因此,coroutine实际上是一组链式的旋律,因此,在链的底部,等待那些等待的物体must return non-coroutine iterators。因此,程序员必须将发电机作为那些迭代器返回(嗯,这就是我们使用本地coroutines的原因),但这并不需要(她或他可以返回非发电机迭代器)。

现在,让我们观察天然的Coroutines如何使用实际的本机coroutine对象进行工作。但是让我们简要讨论与await有关的yield from

插曲:yield from

在阅读PEP 492时,您必须遇到语法yield from <subgenerator>。我没有时间讨论此关键字,但是它只是将当前的控制流传递给<subgenerator>,以便我们可以拥有发电机链。我引入此语法的原因是因为使用await在Coroutine内部调用另一个Coroutine,该await(根据PEP 492)使用与yield from相同的实现。

Coroutine对象的类似发电机的行为

因此,我们有一个本机的coroutine对象和发电机的send()throw()close()。由于文档不断地说“将这些方法委派给迭代器从__await__()返回,所以我想只研究send方法就足以满足我们的目的。

让我们增加一些示例(代码的一部分来自https://stackoverflow.com/a/60118660):

async def await1() -> str:
  print("await 1!!!")
  return "foo"

class Await2:
  def __await__(self) -> str:
    print("before executing Await2")
    val = yield "await from Await2!!!"
    print("value received from send(): ", val)
    val2 = yield "await from Await2!!! - 2"
    print("value received from send(): ", val2)
    return "actual return value"


async def example() -> int:
  print("coroutine runs!")
  print(await await1())
  print("await 1 ended")
  print(await Await2())
  print("Await 2 ended")
  print("end!")
  return 1

if __name__ == "__main__":
  coro = example()
  print("---send None---")
  coro.send(None)

  print("---send 1---")
  coro.send(1)

  print("---send 2---")
  coro.send(2)

将给出这个结果:

---send None---
coroutine runs!
await 1!!!
foo
await 1 ended
before executing Await2
---send 1---
value received from send():  1
---send 2---
value received from send():  2
actual return value
Await 2 ended
end!
Traceback (most recent call last):
# omitted

结果与我们的previous discussion about the internal structure of native coroutines一致。如果Coroutine不暂停yieldawait1()),则执行继续持续到它击中yieldreturn”before executing Await2”)。尽管发电机在下一个yield停止,而本地的Coroutines穿越其链式子仪(是的,await不是yield-它更像yield from,允许锁定的Coroutines序列)。在__await__()中遇到yield之后,在Await2中,结果几乎与发电机的结果相似。一个区别是,我们可以在__await__()之外看到这些产生的值(”await from Await2!!!”),而只能看到返回值("actual return value”)。

因此,在这里我们可以确认Coroutine对象的作用不仅仅是specified in the documentation,而是程序员实施了详细信息。也许这就是为什么大多数参考文献(包括我在本系列文章的序言中推荐的参考文献)对Coroutine对象的这些方法没有太多提及 - 没有什么可谈论的!

所以我们到目前为止讨论中的收获是:

  • 本地旋ou在结构上与发电机相似:而不是yield from,它们具有用于连接执行的await
  • 对于await,不仅可以使用Coroutine链接,还可以使用任何Awaitable对象
  • 在Coroutines链条的底部,我们有迭代器,例如带有yield的发电机

因此,您可以在python中理解一个本机的coroutine对象 - 在异步编程的背景下 - 作为一个具有执行信息的对象,可与其他coroutines和其他等待的对象链接,它可以产生当前的控制流并等待直到这些对象等待对象返回值,就像生成器对象对yield from一样。

结论 - 现在剩下什么?

您可能已经想到了为什么关于Coroutines和Asyncio的大多数讨论都没有解释过本地Coroutine对象,而是突然转到asyncio库(或任何其他类似的库(如triocurio))。现在这是有道理的 - 本地coroutine只是一个框架或接口,这些库填充了实际的行为。

但是,对于我们的详细信息,我们甚至要调查一个库也应该令人生畏。因此,在下一篇文章中,我们将选择其中一个,简要概述如何使用几个相关且重要的概念来实现异步工作流程。