最近通过的PEP-0492为 Python 3.5 在处理协程时增加了一些特殊的语法。新功能中很大一部分在3.5 之前的版本就已经有了,不过之前的语法并不算最好的,因为生成器和协程的概念本身就有点容易混淆。PEP-0492 通过使用 async 关键字显式的对生成器和协程做了区分。
本文旨在说明这些新的机制在底层是如何工作的。如果你只是对怎么使用这些功能感兴趣,那我建议你可以忽略这篇文章,而是去看一下内置的 asyncio 模块的文档。如果你对底层的概念感兴趣,关心这些底层功能如何能构建你自己的 asyncio 模块,那你会发现本文会有有意思。
本文中我们会完全放弃任何异步 I/O 方法,而只限于使用多协程的交互。下面是两个很小的函数:
1 2 3 4 5 6 7 8 9 10 |
def coro1(): print("C1: Start") print("C1: Stop") def coro2(): print("C2: Start") print("C2: a") print("C2: b") print("C2: c") print("C2: Stop") |
我们从两个最简单的函数开始,coro1和coro2。我们可以按顺序来执行这两个函数:
1 2 |
coro1() coro2() |
我们得到期望的输出结果:
1 2 3 4 5 6 7 |
C1: Start C1: Stop C2: Start C2: a C2: b C2: c C2: Stop |
不过,基于某些原因,我们可能会期望这些代码交互运行。普通的函数做不到这点,所以我们把这些函数转换成携程:
1 2 3 4 5 6 7 8 9 10 |
async def coro1(): print("C1: Start") print("C1: Stop") async def coro2(): print("C2: Start") print("C2: a") print("C2: b") print("C2: c") print("C2: Stop") |
通过新的 async 关键字的魔法,这些函数不再是函数了,现在它们变成了协程(更准确的说是本地协程函数)。普通函数被调用的时候,函数体会被执行,但是在调用协程函数的时候,函数体并不会被执行,你得到的是一个协程对象:
1 2 3 |
c1 = coro1() c2 = coro2() print(c1, c2) |
输出:
1 |
<coroutine object coro1 at 0x10ea60990> <coroutine object coro2 at 0x10ea60a40> |
(解释器还会打印一些运行时的警告信息,先忽略掉)。
那么,为什么要有一个协程对象?代码到底如何执行?执行协程的一种方式是使用 await 表达式(使用新的 await 关键字)。你可能会想,可以这样来做:
1 |
await c1 |
不过,你肯定会失望了。await 表达式只有在本地协程函数里才是有效的。你必须这样做:
1 2 |
async def main(): await c1 |
接下来问题来了,main 函数又是如何开始执行的呢?
关键之处是协程确实是与 Python 的生成器非常相似,也都有一个 send 方法。我们可以通过调用 send 方法来启动一个协程的执行。
1 |
就已经有了,不过之前的语法并不算最好的,因为生成器和协程的概念本身就有点容易混淆。PEP-0492 通过使用 async 关键字显式的对生成器和协程做了区分。
本文旨在说明这些新的机制在底层是如何工作的。如果你只是对怎么使用这些功能感兴趣,那我建议你可以忽略这篇文章,而是去看一下内置的 asyncio 模块的文档。如果你对底层的概念感兴趣,关心这些底层功能如何能构建你自己的 asyncio 模块,那你会发现本文会有有意思。 本文中我们会完全放弃任何异步 I/O 方法,而只限于使用多协程的交互。下面是两个很小的函数:
我们从两个最简单的函数开始,coro1和coro2。我们可以按顺序来执行这两个函数:
我们得到期望的输出结果:
不过,基于某些原因,我们可能会期望这些代码交互运行。普通的函数做不到这点,所以我们把这些函数转换成携程:
通过新的 async 关键字的魔法,这些函数不再是函数了,现在它们变成了协程(更准确的说是本地协程函数)。普通函数被调用的时候,函数体会被执行,但是在调用协程函数的时候,函数体并不会被执行,你得到的是一个协程对象:
输出:
(解释器还会打印一些运行时的警告信息,先忽略掉)。 那么,为什么要有一个协程对象?代码到底如何执行?执行协程的一种方式是使用 await 表达式(使用新的 await 关键字)。你可能会想,可以这样来做:
不过,你肯定会失望了。await 表达式只有在本地协程函数里才是有效的。你必须这样做:
接下来问题来了,main 函数又是如何开始执行的呢? 关键之处是协程确实是与 Python 的生成器非常相似,也都有一个 send 方法。我们可以通过调用 send 方法来启动一个协程的执行。
这样我们的第一个协程终于可以执行完成了,不过我们也得到了一个讨厌的 StopIteration 异常: |