python3 async await yield from 异步编程理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yangxiaodong88/article/details/81900985

协程进化

几个关键词的进化
yield yield from asyncio.coroutine, async await future 这几个词要理解

要理解迭代器 生成器 协程之间的区别

python 生成器 协程的发展历程: 先是引入简单的生成器, 接着赋予生成器send() 方法, 使其可以传递数据, 这样协程也有了实际的意义, 接下来 增加了yield from 语法, 简化了调用子生成器的语法, 然后将协程和生成器分开, 这样不易用错。PEP525 提供了异步生成器, 使得编写异步的数据产生器得到简化。

yield from 协程

如果一个生成器内部需要遍历另外一个生成器, 并将数据返给调用者, 你需要遍历并处理所遇到的异常, 而使用yield from 一行就可以处理这样的问题。

调用 yield from gen 时候我们无法判定我遍历了一个生成器还是调用了一个协程。这种混淆使得接口的设计者和使用者者需要花费额外的工作来约定和检查。

于是Python先后添加了 asyncio.coroutine 和types.coroutine这两个装饰器来标识协程, 有这两个装饰就是协程, 这样我们在使用协程的时候不至于误用了生成器。顺带一提,前者是 asyncio 库的实现,需要保持向下兼容,本文暂不讨论;后者则是 Python 3.5 的语言实现,实际上是给函数的 code.co_flags 设置 CO_ITERABLE_COROUTINE 标志。随后,async def 也被引入以用于定义协程,它则是设置 CO_COROUTINE 标志

这样协程和生成器就得到了区分, 其中以types.coroutine定义的协程称为 基于生成器的协程(generator-based coroutine) 而以saync def 定义的协程成为原生协程(native-coroutine)。

这两个协程之间的区别:

  • 原生协程里面不能有yield 或yield from 表达式
  • 原生协程被垃圾回收时候, 如果他还没有被使用过(即 调用await coro 或者await send(NOne)) 会抛出RuntimeWaring
  • 原生协程没有实现 iter和next 方法
  • 简单的生成器(非协程) 不能yield from 原生协程
  • 对原生协程及其函数分别调用inspect.isgenerator() 和inspect.isgeneratorfunction() 将返回false

定义好协程函数之后就可以调用他们了。await 可以用来调用协程, 他的用法和yield from 差不多但是踏只能在协程内部使用, 且只能接awaitable 的对象。 awaitable 对象就是其 await 方法返回一个迭代器的对象。 原生协程和基于生成器的协程都是awaitable的对象。
另外一种调用协程的方法和生成器一样 调用send 方法并自行迭代。这种方式主要是用在非协程函数中调用协程

看一段代码


@types.coroutine
def generator_coroutine():
    yield 1

async def native_coroutine():
    await generator_coroutine()

def main():
    native_coroutine().send(None)

其中 generator_coroutine 函数里因为用到了 yield 表达式,所以只能定义成基于生成器的协程;native_coroutine 函数由于自身是协程,可以直接用 await 表达式调用其他协程;main 函数由于不是协程,因而需要用 native_coroutine().send(None) 这种方式来调用协程。

即为什么原生协程不能「真正的暂停执行并强制性返回给事件循环」。
假设事件循环在 main 函数里,原生协程是 native_coroutine 函数,那要怎么才能让它暂停并返回 main 函数呢?

很显然 await generator_coroutine() 是不行的,这会进入 generator_coroutine 的函数体,而不是回到 main 函数;如果 yield 一个值,又会遇到之前提到的一个限制,即原生协程里不能有 yield 表达式;最后仅剩 return 或 raise 这两种选择了,但它们虽然能回到 main 函数,却也不是「暂停」,因为再也没法「继续」了。

所以一般而言,如果使用Python 3.5 做异步编程的话,最外层的时间循环需要调用协程的send 方法, 里面大部分的异步方法都可以使用原生的协程来实现, 但是最底层的异步方法要基于生成器的协程。

两个漏掉知识点
1 async with
打开文件读取文件

with open('a.txt') as f:
    content = f.read()

异步打开和读取函数的话

async with async_open('a.txt') as f:
    content = await f.read()

2 async for

f = open('1.txt')
async for line in f:
    print(line)

相应的,所需实现的方法分别变成了 aiteranext。其中,后者是异步方法。顺带一提,PEP 525 引入的异步生成器(asynchronous generator)就实现了这两个方法。在异步方法中使用 yield 表达式,会将它变成异步生成器函数(Python 3.6 以后可用,3.5 之前是语法错误)。值得注意的是,异步生成器没有实现 await 方法,因此它不是协程,也不能被 await。

猜你喜欢

转载自blog.csdn.net/yangxiaodong88/article/details/81900985
今日推荐