python 3.5之后,以async def定义的函数是协程函数,调用它生成的协程被称之为native coroutine,在之前都是把生成器函数装饰一下得到一个协程函数,调用它生成的协程是旧式协程,当然生成器、旧式协程和native coroutine本质都一样,但是当我们说到生成器和协程的时候,侧重的是它们的使用场景的不同,生成器侧重的是迭代式得产出一个个值,生成器就像个producer,调用生成器的是consumer,而协程是作为一个轻量级的线程来使用在异步编程领域,所以有必要把它们区分开来。新式语法async def定义的函数里面使用await替代了yield from,其实它们没多少差别,只是改头换面换了个关键字,await更体现异步编程,它字面意义是等待的意思,表示等待一个异步操作完成,协程本身就是为异步编程而生的。
await和yield from在使用的时候又有点不同之处,await后面只能跟awaitable对象,它可以是一个native coroutine,可以是一个旧式协程,可以是一个实现了__await__方法的对象(要求__await__方法返回一个生成器,但经过测试其实是个迭代器也可以),不能是其他的生成器、可迭代对象。总得来说,以async、await定义的新式协程是专门服务于asyncio这样的异步编程框架的,如果你不使用asyncio,基本上也用不到新式协程,当然你也可以使用新式协程实现一个自己的异步框架,那另当别论。asyncio框架,我后面会开一个系列专门讲它的代码实现。
这里要讲一个更加新鲜的玩意,异步生成器。
async def f():
r = await …
m = yield r
r = await …
yield r
return
上面就是一个异步生成器函数。异步生成器是我接触到的python里面最抽象的语法了,只需要几行代码,它内部却是干了许多的事情,不懂得它的运行逻辑,将会非常困惑和难于接受和使用它,因为不知道在什么时候用它,不知道怎么用。
异步生成器也是生成器,既然叫它生成器,自然是侧重于它一个个产出值的特征,但它和生成器不同之处在于它是异步的,它是以异步的方式一个个产出值,也就是产出一个值之前可能需要await一个异步操作被完成。消费它产出的值,通常以一个异步循环来实现。
async for i in f():
visit(i)
异步生成器和生成器不同之处在于,在异步生成器当中yield不再是产出一个值给上游调用者,而是抛出一个StopIteration异常,返回的值以异常的value属性被附带,异步生成器里的yield就像普通生成器的return,在异步生成器中return不再是抛出一个StopIteration异常,而是抛出一个新的StopAsyncIteration异常。
异步生成器也提供了它的close、send、throw方法,不过名字改成了aclose、asend、athrow了,它们的调用逻辑跟普通生成器大为不同了。
下面逐个讲解它们的作用。
异步生成器其实是个双重的生成器,通过调用异步生成器的asend或athrow/aclose,得到一个async_generator_asend或async_generator_athrow对象,它们都是awaitable对象,它们有一个属性(ags_gen或agt_gen)指向该异步生成器,它们都是可执行的对象,它们就相当于一个native coroutine一样,它们依次执行异步生成器函数代码的一个片段,这些片段序列是代码的起始到第一个yield,一个yield到下一个yield以及最后一个yield到最后的return,每一个片段相当于一个coroutine,下面给出一个示例,来展示这一点。asend和athrow不同之处在于给异步生成器暂停的yield位置是传一个值还是传一个异常。要注意,这些awaitable对象,它们生成的顺序不重要,它们被执行的顺序才重要。
from collections import UserList
class AwaitList(UserList):
__await__ = UserList.__iter__
async def f():
await AwaitList((1, 2, 3))
yield 1
await AwaitList((4, 5))
yield 2
def visit(i):
print("async generator yield value: ", i)
async def asyncfor():
asg = f()
while True:
cr = asg.asend(None)
try:
r = await cr
visit(r)
except StopAsyncIteration:
break
#等价于上面的asyncfor
async def _asyncfor():
async for i in f():
visit(i)
afor = asyncfor()
while True:
try:
r = afor.send(None)
print(r)
except StopIteration:
break
调用aclose,它的目的是为了关闭异步生成器。一个正在running(已经产出值了)的异步生成器,不能对他aclose,否则会抛出RunTimeError异常,一旦对异步生成器调用了aclose,它返回一个async_generator_athrow对象,调用它(send、close、throw)将会得到一个StopIteration异常,后续再对异步生成器调用aclose、athrow、asend得到的对象,调用(send、close、throw)它们将会得到一个StopAsyncIteration异常。
最后让我们来想一想,为何python为何要给出异步生成器这一语法特性。
async def f():
r = await …
m = yield r
r = await …
yield r
return
仍然还是这个函数,假设没有异步生成器语法,yield仍然是产出一个值,return仍然是抛出一个StopIteration异常,如果上游调用者要拿到f函数里面yield的值r,会怎么样?会出现困难,因为函数里面await作用的子awaitable对象也会yield值出来,上游调用者无法区分这些值哪个是自己想要的,除非对yield出来的值附加一些特征,这就增加了复杂性了。