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

使用我见过的asyncio 库,

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

async def function():
    ...

可以互换使用。

两者在功能上有区别吗?

【问题讨论】:

    标签: python python-3.x async-await python-3.5 python-asyncio


    【解决方案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语法:

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

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

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

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

      4. 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.
      猜你喜欢
      • 2017-03-27
      • 2017-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-17
      • 2021-09-30
      • 2018-05-02
      • 2020-09-03
      相关资源
      最近更新 更多