Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

asyncio's call_later raises 'generator' object is not callable with coroutine object

I have some simple code code made with Python 3.4's asyncio using call_later. The code should print, waits 10 seconds, and then print again (but instead raises TypeError when end() should be excecuted, see below):

import asyncio

@asyncio.coroutine
def begin():
    print("Starting to wait.")
    asyncio.get_event_loop().call_later(10, end())

@asyncio.coroutine
def end():
    print("completed")

if __name__ == "__main__":
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(begin())
        loop.run_forever()
    except KeyboardInterrupt:
        print("Goodbye!")

Gives the error:

Exception in callback <generator object coro at 0x7fc88eeaddc8>()
handle: <TimerHandle when=31677.188005054 <generator object coro at 0x7fc88eeaddc8>()>
Traceback (most recent call last):
  File "/usr/lib64/python3.4/asyncio/events.py", line 119, in _run
    self._callback(*self._args)
TypeError: 'generator' object is not callable

From what I can tell from the docs (https://docs.python.org/3/library/asyncio-task.html#coroutine), call_later takes a coroutine object, which is obtained by calling a coroutine function. This appears to be what I've done, but asyncio does not call end() properly.

How is this supposed to be done?

like image 631
Nathanael Farley Avatar asked Nov 30 '15 17:11

Nathanael Farley


1 Answers

call_later is designed to take a callback (meaning a regular function object), not a coroutine. Newer versions of Python will actually say this explicitly:

Starting to wait.
Task exception was never retrieved
future: <Task finished coro=<coro() done, defined at /usr/lib/python3.4/asyncio/coroutines.py:139> exception=TypeError('coroutines cannot be used with call_at()',)>
Traceback (most recent call last):
  File "/usr/lib/python3.4/asyncio/tasks.py", line 238, in _step
    result = next(coro)
  File "/usr/lib/python3.4/asyncio/coroutines.py", line 141, in coro
    res = func(*args, **kw)
  File "aio.py", line 6, in begin
    asyncio.get_event_loop().call_later(10, end())
  File "/usr/lib/python3.4/asyncio/base_events.py", line 392, in call_later
    timer = self.call_at(self.time() + delay, callback, *args)
  File "/usr/lib/python3.4/asyncio/base_events.py", line 404, in call_at
    raise TypeError("coroutines cannot be used with call_at()")
TypeError: coroutines cannot be used with call_at()

To make your code work, end needs to be a regular function, which you then pass to call_later:

import asyncio

@asyncio.coroutine
def begin():
    print("Starting to wait.")
    asyncio.get_event_loop().call_later(10, end)

def end():
    print("completed")

if __name__ == "__main__":
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(begin())
        loop.run_forever()
    except KeyboardInterrupt:
        print("Goodbye!")

Output:

Starting to wait.
completed
Goodbye!

If end needs to be a coroutine, a more natural way to call it after a delay would be to use asyncio.sleep:

import asyncio

@asyncio.coroutine
def begin():
    print("Starting to wait.")
    yield from asyncio.sleep(10)
    yield from end()

@asyncio.coroutine
def end():
    print("completed")

if __name__ == "__main__":
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(begin())
        loop.run_forever()
    except KeyboardInterrupt:
        print("Goodbye!")

Though technically, this does work:

asyncio.get_event_loop().call_later(10, lambda: asyncio.async(end()))
like image 60
dano Avatar answered Nov 09 '22 23:11

dano