彻底理解异步生成器

       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出来的值附加一些特征,这就增加了复杂性了。       

  • 35
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__空无一人__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值