Guido,

I’m with Andrew on this one.

> On Oct 31, 2016, at 10:27 AM, Guido van Rossum <gu...@python.org> wrote:
> 
> I would suggest different guidelines for libraries than for
> applications: Libraries should be robust and always store their own
> loop. This is how asyncio itself works and how aiohttp (and other
> libraries you name) work. Their test suite (like asyncio's) should
> enforce this.

>  But applications (and fledgling libraries may do this too) should be
> allowed to assume a simpler model of the world: use coroutines (async
> def, await) for everything, never store the loop, happily use
> get_event_loop() when you really need it. And really, you should only
> need it for run_in_executor(). If you find yourself using call_later()
> you probably haven't quite figured out how to use coroutines properly.

I don’t think people will follow this advice, because any application code can 
one day be refactored to become reusable (a library).  That’s why I write my 
asyncio code defensively, passing the loop explicitly.  This is probably the 
only aspect about asyncio I don’t like.  This is the main complaint about 
asyncio that we hear *repeatedly* from all kinds of users.

The core reason causing all these problems is that get_event_loop is weakly 
defined.  It doesn’t always return the current loop when called from a 
coroutine.  It might return the wrong one, that just happens to be the 
“default”.

I know that you defend the current behaviour by saying that people should only 
have one loop per process.  But some people have many in one process.  That’s 
why we design libraries to handle the loop explicitly — that ensures that the 
library will work regardless of what get_event_loop returns.

The result is that all asyncio libraries accept the “loop” parameter in their 
public APIs. All of them are architected to pass the loop explicitly 
internally.  And then, the asyncio end users aren’t sure how to use asyncio.  
Python docs recommend (sometimes indirectly) to pass the loop explicitly.   
Libraries and frameworks recommend that as well.

The end result of this is that asyncio programs always care “too much” about 
the loop: mange it, store references to it, explicitly pass it to coroutines.  
This makes asyncio code harder to understand to both advanced and beginner 
users.

My opinion on this:

1. We need to fix “get_event_loop” to return the *current* event loop when 
called from within a coroutine:

  async def coro():
     loop = asyncio.get_event_loop()

This is something we can easily implement, because coroutines are *always* 
running under some event loop.

2. We should explain asyncio library authors that it is better to “hide” the 
loop from the high-level API.  The high level API should only consist of 
coroutines, that don’t have a “loop” parameter at all.  Because get_event_loop 
is guaranteed to always return the correct loop, it will be safe to use it.

So instead of:

  async def main(loop):
     db = asyncpg.connect(…, loop=loop)
  loop = asyncio.get_event_loop()
  loop.run_until_complete(main(loop))

people will always write this:

  async def main():
     db = asyncpg.connect(…)
  loop = asyncio.get_event_loop()
  loop.run_until_complete(main())

3. Docs should say that the event loop is a low-level API.  As you said, if you 
call “loop.call_soon” from your application code, then something is wrong.

If we can shift asyncio libraries to be designed around coroutine-first API, we 
can safely start caring much less about the loop.  We will only use it to run 
the main() coroutine of the program.

This is something that curio does right — the event loop is what runs the 
program, but the end user knows pretty much nothing about it. Coroutines just 
work, because curio provides a *reliable* way for getting the *current* loop 
from within a running coroutine context.

This is a fully backwards compatible change.  We even have a PR to do this: 
https://github.com/python/asyncio/pull/355.  That PR might need another review 
pass, but the idea is there.

Thank you,
Yury

Reply via email to