classification
Title: Deprecate get_event_loop()
Type: Stage:
Components: asyncio Versions:
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: aeros, asvetlov, serhiy.storchaka, xtreak, yselivanov
Priority: normal Keywords:

Created on 2020-02-02 11:11 by asvetlov, last changed 2020-02-23 02:57 by aeros.

Messages (7)
msg361229 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2020-02-02 11:11
Yuri proposed it for Python 3.8 but at that time the change was premature.
Now we can reconsider it for 3.9

The problem is that asyncio.get_event_loop() not only returns a loop but also creates it on-demand if the thread is main and the loop doesn't exist.  

It leads to weird errors when get_event_loop() is called at import-time and asyncio.run() is used for asyncio code execution.

get_running_loop() is a much better alternative when used *inside* a running loop, run() should be preferred for calling async code at top-level. Low-level new_event_loop()/loop.run_until_complete() are still present to run async code if top-level run() is not suitable for any reason.

asyncio.run() was introduced in 3.7, deprecation on get_event_loop() in 3.8 was able to complicate support of 3.5/3.6 by third-party libraries.
3.5 now reached EOL, 3.6 is in the security-fix mode and going close to EOL. Most people are migrated to newer versions already if they care.
The maintenance burden of the introduced deprecation should be pretty low.
msg361245 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2020-02-02 16:11
Will asyncio.get_event_loop() be removed or made an alias of asyncio.get_running_loop()? The latter could minimize the disruption of the existing code.
msg361247 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2020-02-02 16:44
Currently, I'm talking about adding a deprecation only.
The asyncio.get_event_loop() function will stay in Python for a while in deprecated status. I don't know the exact period but it should be 3 releases at least I guess, with possibly extending to 5 releases if needed.
msg361248 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2020-02-02 16:49
Serhiy, maybe I had not understood your proposal properly.

If you are asking about deprecating get_event_loop() call from outside of async code but implicit call get_running_loop() without a warning if called from async function -- it sounds like a brilliant idea.

Something like:

def get_event_loop():
    current_loop = _get_running_loop()
    if current_loop is not None:
        return current_loop
    warnings.warn("...", DeprecationWarning)  
    return get_event_loop_policy().get_event_loop()
msg361249 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2020-02-02 17:12
Yes, it is what I meant.

Many code written before 3.7 use get_event_loop() because there was no get_running_loop() yet. Now, to avoid deprecation (and making the code more robust) it should be rewritten with using get_running_loop() or get_event_loop() depending on Python version. It is cumbersome, and causes a code churn.
msg361609 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2020-02-07 19:32
I think we still use get_event_loop() in asyncio code, no? If we do, we should start by raising deprecation warnings in those call sites, e.g. if a Future or Lock is created outside of a coroutine and no explicit event loop is passed. We should do this in 3.9. We can then think about deprecating get_event_loop in 3.10.
msg362487 - (view) Author: Kyle Stanley (aeros) * (Python triager) Date: 2020-02-23 02:57
FWIW, I agree with get_event_loop() being problematic with its error messages, and having a bit too much functionality in a single function from an API design perspective. It commonly comes up as an issue among asyncio users in communities that I'm active in. 

Typically, I advise them to use get_running_loop() where possible, as it can be directly substituted within a coroutine func. For __init__ methods, I recommend setting their internal loop attribute to None and then setting it to get_running_loop() in either a coro start() method or coro factory method. Outside of directly managing the event loop, this works for many use cases. When it's needed to actually create one, I advise them to use new_event_loop(), but mention that it's already handled if they're using asyncio.run().

Andrew Svetlov wrote:
> The asyncio.get_event_loop() function will stay in Python for a while in deprecated status. I don't know the exact period but it should be 3 releases at least I guess, with possibly extending to 5 releases if needed.

With how many libraries that rely on it, I suspect it will likely be a very slow transition from deprecation to removal. 4 versions seems like a reasonable period to me, but I think that 3 may be too short (assuming we retain the newer annual release cycle).

Yury Selivanov wrote:
> I think we still use get_event_loop() in asyncio code, no?

Indeed, we currently use it in several places throughout asyncio. From a brief glace using git grep "get_event_loop()":

Lib/asyncio/futures.py:76:            self._loop = events.get_event_loop()
Lib/asyncio/futures.py:390:        loop = events.get_event_loop()
Lib/asyncio/locks.py:81:            self._loop = events.get_event_loop()
Lib/asyncio/locks.py:177:            self._loop = events.get_event_loop()
Lib/asyncio/locks.py:244:            self._loop = events.get_event_loop()
Lib/asyncio/locks.py:375:            self._loop = events.get_event_loop()
Lib/asyncio/queues.py:35:            self._loop = events.get_event_loop()
Lib/asyncio/streams.py:45:        loop = events.get_event_loop()
Lib/asyncio/streams.py:82:        loop = events.get_event_loop()
Lib/asyncio/streams.py:104:            loop = events.get_event_loop()
Lib/asyncio/streams.py:120:            loop = events.get_event_loop()
Lib/asyncio/streams.py:147:            self._loop = events.get_event_loop()
Lib/asyncio/streams.py:403:            self._loop = events.get_event_loop()
Lib/asyncio/subprocess.py:206:        loop = events.get_event_loop()
Lib/asyncio/subprocess.py:227:        loop = events.get_event_loop()
Lib/asyncio/tasks.py:69:        loop = events.get_event_loop()
Lib/asyncio/tasks.py:129:            loop = events.get_event_loop()
Lib/asyncio/tasks.py:590:        loop = events.get_event_loop()
Lib/asyncio/tasks.py:669:            loop = events.get_event_loop()
Lib/asyncio/tasks.py:751:            loop = events.get_event_loop()

For brevity, I omitted the docs, tests, and the function definition for get_event_loop().

Based on Serhiy's idea (of making get_event_loop() an alias for get_running_loop() without warning inside of a coro func, but warning for using it outside of one), many of the above could remain as is to reduce some code churn. We just have to make sure the documentation is updated to reflect get_event_loop() becoming an alias for get_running_loop(), at the same time as the deprecation warning is added for using it outside of a coro func. Otherwise, I suspect it could lead to significant confusion from users that have warnings enabled.

That being said, I think we should eventually remove asyncio.get_event_loop() entirely from the asyncio internals, including the ones that wouldn't raise deprecation warnings (if/when it's made an alias to get_running_loop()) for improved clarity. Personally, I find that even the name get_event_loop() is rather vague; get_running_loop() is much more obvious as to its purpose and what it does from a readability perspective.

Yury Selivanov wrote:
> If we do, we should start by raising deprecation warnings in those call sites, e.g. if a Future or Lock is created outside of a coroutine and no explicit event loop is passed.

For asyncio.Lock (plus other synchronization primitives) and asyncio.Queue, this would be added in https://github.com/python/cpython/pull/18195. Currently waiting on emanu (author of the PR) to finish up some changes, but it's mostly complete. Could I work on adding it to asyncio.Future and other classes in the meantime?

One point to be clarified though: you mention "created outside of a coroutine and no explicit event loop is passed". However, there are already several deprecations in place for passing an explicit event loop for most (if not all) of the __init__ methods for objects across asyncio's high-level API. In those cases, should the deprecation for creating the object outside of a coroutine function care about whether or not an explicit event loop is passed?

I can see why it would matter for the lower level parts of the API (such as asyncio.Future) where passing the event loop explicitly is still allowed, but IMO it shouldn't be a factor for ones where passing the event loop explicitly is already deprecated. Especially considering that the loop argument will be removed from those entirely in 3.10 (according to the version listed in the current deprecation warnings added in 3.8).
History
Date User Action Args
2020-02-23 02:57:28aerossetnosy: + aeros
messages: + msg362487
2020-02-07 19:32:10yselivanovsetmessages: + msg361609
2020-02-07 19:05:39xtreaksetnosy: + xtreak
2020-02-02 17:12:56serhiy.storchakasetmessages: + msg361249
2020-02-02 16:50:06asvetlovsetnosy: + yselivanov
components: + asyncio
2020-02-02 16:49:58asvetlovsetmessages: + msg361248
2020-02-02 16:44:08asvetlovsetmessages: + msg361247
2020-02-02 16:11:17serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg361245
2020-02-02 11:11:37asvetlovcreate