54

With the asyncio library I've seen,

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

and

async def function():
    ...

used interchangeably.

Is there any functional difference between the two?

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Jason
  • 2,278
  • 2
  • 17
  • 25

3 Answers3

68

Yes, there are functional differences between native coroutines using async def syntax and generator-based coroutines using the asyncio.coroutine decorator.

According to PEP 492, which introduces the async def syntax:

  1. Native coroutine objects do not implement __iter__ and __next__ methods. Therefore, they cannot be iterated over or passed to iter(), list(), tuple() and other built-ins. They also cannot be used in a for..in loop.

    An attempt to use __iter__ or __next__ on a native coroutine object will result in a TypeError .

  2. Plain generators cannot yield from native coroutines: doing so will result in a TypeError .

  3. generator-based coroutines (for asyncio code must be decorated with @asyncio.coroutine) can yield from native coroutine objects.

  4. inspect.isgenerator() and inspect.isgeneratorfunction() return False for native coroutine objects and native coroutine functions.

Point 1 above means that while coroutine functions defined using the @asyncio.coroutine decorator syntax can behave as traditional generator functions, those defined with the async def syntax cannot.

Here are two minimal, ostensibly equivalent coroutine functions defined with the two syntaxes:

import asyncio

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

async def native(x):
    await x 

Although the bytecode for these two functions is almost identical:

>>> 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

... the only difference being GET_YIELD_FROM_ITER vs GET_AWAITABLE, they behave completely differently when an attempt is made to iterate over the objects they return:

>>> 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

Obviously 'foo' is not an awaitable, so the attempt to call native() with it doesn't make much sense, but the point is hopefully clear that the coroutine object it returns is not iterable, regardless of its argument.

A more detailed investigation of the async/await syntax by Brett Cannon: How the heck does async/await work in Python 3.5? covers this difference in more depth.

Quentin Pradet
  • 4,691
  • 2
  • 29
  • 41
Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
22

async def is a new syntax from Python 3.5. You could use await, async with and async for inside async defs.

@coroutine is a functional analogue for async def but it works in Python 3.4+ and utilizes yield from construction instead of await.

For practical perspective just never use @coroutine if your Python is 3.5+.

Andrew Svetlov
  • 16,730
  • 8
  • 66
  • 69
  • 1
    Would be good to know why exactly "never use @coroutine" in 3.5+. Is there a real reason or is it an opinion/rule of thumb? For example, I have some @coroutine decorators in a 3.4 inherited codebase but all new development is in 3.5. Should I be turning those decorators into `async def`? – hmijail Oct 28 '17 at 08:50
  • To suspend coroutine execution it is necessary to `yield`. But `yield` is not allowed from inside native coroutines! So looks like it's still necessary to use `@coroutine` even in Python 3.5+ – lesnik Dec 13 '17 at 08:29
  • 1
    No. Just use `await asyncio.sleep(0)`. – Andrew Svetlov Dec 13 '17 at 10:21
  • @hmijail `@coroutine` marked generators could be awaited but better to convert them into `async def` if possible. – Andrew Svetlov Dec 13 '17 at 10:23
  • @AndrewSvetlov `asyncio.sleep` must be using `yield` somewhere inside, doesn't it? So, `@coroutine`'s wouldn't be gone from the language. But I agree that if you are using `asyncio` or some other framework you do not have to (and most probably should not) use `@coroutine`. – lesnik Dec 13 '17 at 11:09
  • 1
    `await asyncio.sleep(10)` works pretty well. Moreover I've converted `asyncio.sleep()` into `async def` in asyncio master (will be released as part of Python 3.7). – Andrew Svetlov Dec 14 '17 at 05:57
  • @AndrewSvetlov This is really interesting! How that would work? As far as I understand, sooner or later `yield` must be called to yield control, so somewhere deep inside `@coroutine` must be used. Or am I wrong? – lesnik Dec 14 '17 at 15:26
3

From Python 3.5 coroutines formally became a distinct type and thus the async def syntax, along with await statements.

Prior to that, Python 3.4 created coroutines by wrapping regular functions into generators, hence the decorator syntax, and the more generator-like yield from.

songololo
  • 4,724
  • 5
  • 35
  • 49