Nishant
Nishant

Reputation: 21914

Why do we need the asyncio.coroutine decorator?

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?

Upvotes: 16

Views: 4268

Answers (2)

nickelpro
nickelpro

Reputation: 2917

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.

Upvotes: 3

Mikhail Gerasimov
Mikhail Gerasimov

Reputation: 39546

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

Upvotes: 11

Related Questions