Why do we need the asyncio.coroutine decorator?
Asked Answered
M

2

16

Why do we need the asyncio.coroutine decorator? What functionality does it provide?

For example:

# import asyncio
# @asyncio.coroutine
def gen():
    value = yield("Started")
    print(value)

a = gen()
a.send(None)
a.send("Done")

Now if I uncomment the first two lines and use the asyncio.coroutine decorator, I still get the same output.

I mean this is already a coroutine - a function that can be paused and passed in with an argument. Why do I need to decorate it with another coroutine i.e asyncio.coroutine?

Midwife answered 5/11, 2017 at 10:57 Comment(7)
The async def and await syntax wasn't available until 3.5 while asyncio was formally introduced in 3.4...Cygnus
But still what value does the asyncio.coroutine decorator add to my code? I can use the generator as a coroutine without additional code right. [My next question is why do we need the async def and await as a syntax when it can be achieved without that. But I will not mix that with this question.]Midwife
Now I'm not sure what you're asking... you have a normal generator function there that you're sending data back in to - you've been able to do that before any asyncio at all...Cygnus
Ok and that is what a coroutine is, it doesn't need asyncio. So when we decorate a coroutine with asyncio.coroutine do we add some value to it? For example Twisteds inlineCallbacks decorator can be used to trigger and run a coroutine till it ends. Something like it accepts a generator function, executes it to get the generator object ... Then starts it with None etc.Midwife
You can then use them as part of an async event loop and have them wait/sleep/cancel each other etc... If you're familiar with twisted, then think of asyncio IO as a base for doing the same thing (without all the ready made reactors and protocols)... Might be worth having a read through a tutorial or basic examples of it and take it from there...Cygnus
In your example, you don’t need the decorator. It provides you with nothing. You’d only use it if you’re defining a coroutine that will get passed to an event loop. And even then you’d only want to use the decorator if your code is going to run on 3.4; you can use async def starting in 3.5.Havildar
I highly recommend looking at the asyncio source code to try to understand this stuff (in particular asyncio/coroutines.py). It seems like documentation and explanations are more clear after looking at the code (it's like the code is documenation for the explanations...).Duff
E
11

It's important to understand that generators and asyncio coroutines - are different things. Coroutines are implemented using generators, but (theoretically) could been implemented without them. Genarators - are part of implementation regarding to coroutines.

Since asyncio coroutines are implemented using generators, you can sometimes use generators as coroutines without errors:

import asyncio


def main():
    yield from asyncio.sleep(1)
    print('done')


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Result:

done

But it doesn't work with every kind of coroutine:

import asyncio


def main():
    # yield from asyncio.sleep(1)
    print('done')


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Result:

TypeError: An asyncio.Future, a coroutine or an awaitable is required

That's why (besides few other things) asyncio.coroutine decorator uses:

import asyncio


@asyncio.coroutine
def main():
    # yield from asyncio.sleep(1)
    print('done')


loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Result:

done

But as already noted by all it doesn't actually matter today: since Python 3.5 decorator and yield from have been replaced with keywords async def and await that's not only nicer, but helps to split coroutines from their implementation details in a better way.

import asyncio


async def main():
    # await asyncio.sleep(1)
    print('done')


loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Eager answered 5/11, 2017 at 13:7 Comment(1)
Sorry, I'm a little bothered by the line "But it doesn't work with every kind of coroutine." In the code that follows, it seems like you are implying that main is a coroutine... How? It's just a regular function. Also, as far as the "sometimes use generators as coroutines," the implemenation I'm looking at will accept any generator as long as it yields None or yields from something that is accepted as a coroutine. It's probably a bit of an implementation detail, but still more enlightening than "sometimes."Duff
A
3

I'm super late to this party but I was interested from a historical perspective what this decorator did.

The accepted answer is simply wrong, or really a non-sequitur, and editing it would be an entire re-write so better to leave it and answer separately.

For the OPs example, what @asyncio.coroutine did was attach the CO_ITERABLE_COROUTINE flag to the object's co_flags. This allowed the generator to be used with the await keyword and also to yield from "pure" coroutines that were not themselves generator-derived.

# Check if 'func' is a generator function.
# (0x20 == CO_GENERATOR)
if co_flags & 0x20:
  # TODO: Implement this in C.
  co = func.__code__
  func.__code__ = CodeType(
    co.co_argcount, co.co_posonlyargcount, co.co_kwonlyargcount, co.co_nlocals,
    co.co_stacksize,
    co.co_flags | 0x100,  # 0x100 == CO_ITERABLE_COROUTINE
    co.co_code,
    co.co_consts, co.co_names, co.co_varnames, co.co_filename,
    co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars,
    co.co_cellvars)
  return func

The decorator had some other uses, notably (as illustrated in the accepted answer), it would transform a regular function into a coroutine if it was not already a generator.

Assimilable answered 27/11, 2023 at 9:54 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.