[Python-Dev] async/await behavior on multiple calls

Yury Selivanov yselivanov.ml at gmail.com
Tue Dec 15 22:33:13 EST 2015


Roy,

On 2015-12-15 8:29 PM, Roy Williams wrote:
[..]
>
> My proposal would be to automatically wrap the return value from an 
> `async` function or any object implementing `__await__` in a future 
> with `asyncio.ensure_future()`.  This would allow async/await code to 
> behave in a similar manner to other languages implementing async/await 
> and would remain compatible with existing code using asyncio.
>
> What's your thoughts?

Other languages, such as JavaScript, have a notion of event loop 
integrated on a very deep level.  In Python, there is no centralized 
event loop, and asyncio is just one way of implementing one.

In asyncio, Future objects are designed to inter-operate with an event 
loop (that's also true for JS Promises), which means that in order to 
automatically wrap Python coroutines in Futures, we'd have to define the 
event loop deep in Python core.  Otherwise it's impossible to implement 
'Future.add_done_callback', since there would be nothing that calls the 
callbacks on completion.

To avoid adding a built-in event loop, PEP 492 introduced coroutines as 
an abstract language concept.  David Beazley, for instance, doesn't like 
Futures, and his new framework 'curio' does not have them at all.

I highly doubt that we want to add a generalized event loop in Python 
core, define a generalized Future interface, and make coroutines return 
it.  It's simply too much work with no clear wins.

Now, your initial email highlights another problem:

    coro = coroutine()
    print(await coro)  # will print the result of coroutine
    await coro  # prints None

This is a bug that needs to be fixed.  We have two options:

1. Cache the result when the coroutine object is awaited first time. 
Return the cached result when the coroutine object is awaited again.

2. Raise an error if the coroutine object is awaited more than once.

The (1) option would solve your problem.  But it also introduces new 
complexity: the GC of result will be delayed; more importantly, some 
users will wonder if we cache the result or run the coroutine again.  
It's just not obvious.

The (2) option is Pythonic and simple to understand/debug, IMHO.  In 
this case, the best way for you to solve your initial problem, would be 
to have a decorator around your tasks.  The decorator should wrap 
coroutines with Futures (with asyncio.ensure_future) and everything will 
work as you expect.

Thanks,
Yury



More information about the Python-Dev mailing list