@asyncio.coroutine 与 async def

Posted

技术标签:

【中文标题】@asyncio.coroutine 与 async def【英文标题】:@asyncio.coroutine vs async def 【发布时间】:2017-03-27 02:08:36 【问题描述】:

使用我见过的asyncio 库,

@asyncio.coroutine
def function():
    ...

async def function():
    ...

可以互换使用。

两者在功能上有区别吗?

【问题讨论】:

【参考方案1】:

async def 是 Python 3.5 的新语法。 您可以在async defs 中使用awaitasync withasync for

@coroutineasync def 的功能类似物,但它适用于 Python 3.4+ 并使用 yield from 构造而不是 await

如果您的 Python 版本为 3.5+,请不要使用 @coroutine

【讨论】:

很高兴知道为什么在 3.5+ 中“从不使用@coroutine”。有真正的原因还是意见/经验法则?例如,我在 3.4 继承的代码库中有一些 @coroutine 装饰器,但所有新开发都在 3.5 中。我应该把那些装饰器变成async def吗? 要暂停协程执行,需要yield。但是本机协程内部不允许使用yield!所以看起来即使在 Python 3.5+ 中仍然需要使用@coroutine 没有。只需使用await asyncio.sleep(0) @hmijail @coroutine 标记的生成器可以等待,但如果可能的话最好将它们转换为async def await asyncio.sleep(10) 效果很好。此外,我在 asyncio master 中将 asyncio.sleep() 转换为 async def(将作为 Python 3.7 的一部分发布)。【参考方案2】:

Python 3.5 coroutines 正式成为一个独特的类型,因此 async def 语法以及 await 语句。

在此之前,Python 3.4 通过将常规函数包装到 generators 中来创建协程,因此使用了装饰器语法,并且更类似于生成器 yield from

【讨论】:

【参考方案3】:

是的,使用 async def 语法的原生协程和使用 asyncio.coroutine 装饰器的基于生成器的协程之间存在功能差异。

根据PEP 492,其中引入了async def语法:

    本机协程对象不实现 __iter____next__ 方法。因此,它们不能被迭代或传递 到iter()list()tuple() 和其他内置插件。他们也 不能在 for..in 循环中使用。

    尝试在原生协程上使用 __iter____next__ 对象将导致 TypeError 。

    普通生成器不能yield from原生协程:这样做 将导致 TypeError 。

    基于生成器的协程(对于 asyncio 代码必须用 @asyncio.coroutine) 可以yield from 原生协程对象

    inspect.isgenerator()inspect.isgeneratorfunction()本机协程 对象和 本机协程函数返回 False

上面的第 1 点意味着虽然使用 @asyncio.coroutine 装饰器语法定义的协程函数可以像传统的生成器函数一样,但使用 async def 语法定义的协程函数不能。

这里有两个最小的,表面上等效的协程函数,用两种语法定义:

import asyncio

@asyncio.coroutine
def decorated(x):
    yield from x 

async def native(x):
    await x 

虽然这两个函数的字节码几乎相同:

>>> import dis
>>> dis.dis(decorated)
  5           0 LOAD_FAST                0 (x)
              3 GET_YIELD_FROM_ITER
              4 LOAD_CONST               0 (None)
              7 YIELD_FROM
              8 POP_TOP
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE
>>> dis.dis(native)
  8           0 LOAD_FAST                0 (x)
              3 GET_AWAITABLE
              4 LOAD_CONST               0 (None)
              7 YIELD_FROM
              8 POP_TOP
              9 LOAD_CONST               0 (None)
             12 RETURN_VALUE

...唯一的区别是GET_YIELD_FROM_ITERGET_AWAITABLE,当尝试迭代它们返回的对象时,它们的行为完全不同:

>>> list(decorated('foo'))
['f', 'o', 'o']

>>> list(native('foo'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'coroutine' object is not iterable

显然'foo' 不是可等待的,因此尝试用它调用native() 没有多大意义,但希望清楚一点,它返回的coroutine 对象是不可迭代的,不管它的论据。

Brett Cannon 对async/await 语法进行了更详细的调查:How the heck does async/await work in Python 3.5? 更深入地涵盖了这种差异。

【讨论】:

谢谢!但是为什么 inspect.iscoroutinefunction() 返回 False 用于原生协程函数呢? @roachsinai 它doesn't.

以上是关于@asyncio.coroutine 与 async def的主要内容,如果未能解决你的问题,请参考以下文章

async/await

六十五 async/await

Python学习---Python的异步---asyncio模块(no-http)

多线程配合协程

异步IO框架:asyncio 中篇

python3.4的asyncio用法