什么是本地的Coroutine?
从上一篇文章中,我们讨论了如何将发电机视为的coroutines,但是不能被完全接受为Coroutines,并且由于PEP 492,我们终于有了本机构python中的coroutine对象。
如果您阅读了PEP 492或official 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不暂停yield
(await1()
),则执行继续持续到它击中yield
或return
(”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库(或任何其他类似的库(如trio或curio))。现在这是有道理的 - 本地coroutine只是一个框架或接口,这些库填充了实际的行为。
但是,对于我们的详细信息,我们甚至要调查一个库也应该令人生畏。因此,在下一篇文章中,我们将选择其中一个,简要概述如何使用几个相关且重要的概念来实现异步工作流程。