34

A lot of the functions in asyncio have deprecated loop parameters, scheduled to be removed in Python 3.10. Examples include as_completed(), sleep(), and wait().

I'm looking for some historical context on these parameters and their removal.

  • What problems did loop solve? Why would one have used it in the first place?
  • What was wrong with loop? Why is it being removed en masse?
  • What replaces loop, now that it's gone?
Maxpm
  • 24,113
  • 33
  • 111
  • 170
  • Your question is phrased differently, but it is answered here https://stackoverflow.com/questions/40340493/passing-asyncio-loop-by-argument-or-using-default-asyncio-loop - you no longer need/get to pass a specific event loop. – Grismar Feb 20 '20 at 03:46
  • 3
    I don't feel like my question of "what happened and why" is answered over there. I basically read that other question, its comments, and its answer as telling me what to do, without much justification. (Basically, a reiteration of the deprecation warnings I was asking about in the first place.) – Maxpm Feb 20 '20 at 04:47
  • 1
    The article linked in that answer literally answers your questions https://mail.python.org/pipermail/async-sig/2016-November/000171.html - what happened = the global event loop was causing issues, why did it happen = bad design decisions, what was wrong = loop was for passing the global loop, what replaces it = nothing, it's still there but you no longer have to pass it around (solving many of the problems) and if you need it, you can request it as the `current_event_loop()` – Grismar Feb 20 '20 at 04:51
  • 2
    @Grismar An answer to my question might very well be in that article, but it's not in the Stack Overflow answer that links to that article! Nor would I expect it to be – these two questions talk about the same thing, but what I'm asking about it is different. – Maxpm Feb 20 '20 at 05:30

2 Answers2

46

What problems did loop solve? Why would one have used it in the first place?

Prior to Python 3.6, asyncio.get_event_loop() was not guaranteed to return the event loop currently running when called from an asyncio coroutine or callback. It would return whatever event loop was previously set using set_event_loop(some_loop), or the one automatically created by asyncio. But sync code could easily create a different loop with another_loop = asyncio.new_event_loop() and spin it up using another_loop.run_until_complete(some_coroutine()). In this scenario, get_event_loop() called inside some_coroutine and the coroutines it awaits would return some_loop rather than another_loop. This kind of thing wouldn't occur when using asyncio casually, but it had to be accounted for by async libraries which couldn't assume that they were running under the default event loop. (For example, in tests or in some usages involving threads, one might want to spin up an event loop without disturbing the global setting with set_event_loop.) The libraries would offer the explicit loop argument where you'd pass another_loop in the above case, and which you'd use whenever the running loop differed from the loop set up with asyncio.set_event_loop().

This issue would be fixed in Python 3.6 and 3.5.3, where get_event_loop() was modified to reliably return the running loop if called from inside one, returning another_loop in the above scenario. Python 3.7 would additionally introduced get_running_loop() which completely ignores the global setting and always returns the currently running loop, raising an exception if not inside one. See this thread for the original discussion.

Once get_event_loop() became reliable, another problem was that of performance. Since the event loop was needed for some very frequently used calls, most notably call_soon, it was simply more efficient to pass around and cache the loop object. Asyncio itself did that, and many libraries followed suit. Eventually get_event_loop() was accelerated in C and was no longer a bottleneck.

These two changes made the loop arguments redundant.

What was wrong with loop? Why is it being removed en masse?

As any other redundancy, it complicates the API and opens up possibilities for errors. Async code should almost never just randomly communicate with a different loop, and now that get_event_loop() is both correct and fast, there is no reason not to use it.

Also, passing the loop through all the layers of abstraction of a typical application is simply tedious. With async/await becoming mainstream in other languages, it has become clear that manually propagating a global object is not ergonomic and should not be required from programmers.

What replaces loop, now that it's gone?

Just use get_event_loop() to get the loop when you need it. Alternatively, you can use get_running_loop() to assert that a loop is running.

The need for accessing the event loop is somewhat reduced in Python 3.7, as some functions that were previously only available as methods on the loop, such as create_task, are now available as stand-alone functions.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • of course there's no way to create an event now, outside a loop, which is one of the places you need one – Erik Aronesty Apr 04 '22 at 19:12
  • 1
    This has been one of the most helpful answers I've encountered for understanding what to do when writing a library that uses async! Many thanks! – Eddie Bergman Feb 11 '23 at 19:51
2

The loop parameter was the way to pass the global event loop around. New implementations of the same functions no longer require you to pass the global event loop, they instead just request it where it's needed.

As the documentation suggests https://docs.python.org/3/library/asyncio-eventloop.html: "Application developers should typically use the high-level asyncio functions, such as asyncio.run(), and should rarely need to reference the loop object or call its methods."

Removing the need for you to pass it around to library functions aligns with that principle. The loop is not replaced, but its disappearance simply means you no longer have to deal with it 'manually'.

Grismar
  • 27,561
  • 4
  • 31
  • 54