-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate get_event_loop() #83710
Comments
Yuri proposed it for Python 3.8 but at that time the change was premature. 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. |
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. |
Currently, I'm talking about adding a deprecation only. |
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() |
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. |
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. |
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:
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:
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() 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:
For asyncio.Lock (plus other synchronization primitives) and asyncio.Queue, this would be added in #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). |
I think the approach should be different: # leading underscore is significant:
loop = asyncio._get_running_loop()
if loop is None:
issue_deprecation_warning()
loop = asyncio.get_event_loop() |
I am writing a code (it is not trivial to emit correct warning) and I have a question. What to do if the loop was set by set_event_loop()? Currently get_event_loop() can return a loop without creating a new loop while get_running_loop() raises an error. Should get_event_loop() still support this in future or set_event_loop() will be deprecated? |
I think the deprecation of |
The most visible effect in tests is that asyncio.gatcher() and some other synchronous functions will need a running loop if any of arguments is a coroutine. |
This is going to cause an unpleasant amount of churn in the Tornado community. It's been idiomatic (going back 12 years now) to do all your setup in synchronous code before starting the event loop. I'm tempted to restore the implicit loop creation in Tornado's
Checks asyncio.run's implementation Oh, I can see how that's a problem (and I've been giving people bad advice about the interchangeability of Tornado's IOLoop.run_sync and asyncio.run). But why does
What if you're using run_forever instead of run_until_complete? (this is the common mode of operation for Tornado) There are solutions, of course (my preferred one is |
AFAIK, it does so for purposes of compatibility in programs that need multiple separate event loops and providing a degree of self-dependency. asyncio.run() is entirely self-reliant in that it creates all needed resources at the start and closes them in finalization, rather than depending on existing resources. I believe this to be significantly safer and better guaranteed to function as intended, although perhaps at some cost to convenience in cases like your own where there only needs to be one event loop. |
The comments in this thread suggest that I'm trying to understand the consequences of these changes for IPython, and make the changes intended by asyncio folks, but am not quite clear, yet. If I understand it correctly, this means that the whole concept of a 'current' event loop is deprecated while no event loop is running? My interpretation of these changes is that it means any persistent handles on any event loop while it isn't running is fully the responsibility of individual libraries (e.g. tornado, IPython). This is coming up in IPython where we need a handle on the event loop and advance it with |
Further digging reveals that |
IMHO, asyncio.set_event_loop() and policy.get_event_loop()/policy.set_event_loop() are not deprecated by oversight. In IPython, I think you could use new_event_loop() for getting a new loop instance. At my job, we have Runner class, the basic usage is: The implementation is pretty close to asyncio.run() but runner.run(...) can be called multiple times. Async context manager interface is responsible for resource closing. Maybe I should extract the implementation into a third-party library, I've found this concept useful for CLI applications at least. |
Thank you! I think I have enough information to update.
I'm curious, what is an appropriate use of |
Ages ago, get_event_loop() did not return the current running loop if called from async function context; it returned a loop instance registered with previous set_event_loop(...) call instead. Later we fixed get_event_loop() behavior in 3.5.4 IIRC and added get_running_loop() in 3.7 set_event_loop() doesn't make sense now from my perspective. |
Oops, I interpreted "not deprecated by oversight" as the opposite of what you meant. Sorry! All clear, now. |
That works if you're a leaf in the dependency tree, but what about a library like Tornado (which IPython depends on)? If intermediate libraries (say Tornado and Twisted) introduce their own thread-local loops for backwards compatibility of their existing interfaces, the ecosystem gets fragmented again. Having the thread-local live in asyncio is useful for seamless interoperability across frameworks.
Whether or not this is more likely to function as intended depends on assumptions about user expectations. In Tornado (and other async systems I have used in the past), it is the norm to set up handlers on an event loop while it is not running and then start it afterwards. The behavior of asyncio.run is surprising to me. Maybe I'm weird, but regardless of which behavior is surprising to the fewest people, my point is that this change is very disruptive to Tornado and most applications using it, contrary to the claim that the maintenance burden should be pretty low. I realize it may be too late to change course, but my preference would have been to resolve the conflict between get_event_loop and asyncio.run by making asyncio.run essentially an alias for asyncio.get_event_loop().run_until_complete. This would relax the isolation of asyncio.run, but that doesn't seem very important to me. The comparable idioms in Tornado (using the IOLoop.run_sync method) all work this way and I've never seen any confusion related to this or a situation in which creating a brand-new event loop in run_sync would be desirable. |
asyncio.get_event_loop() became deprecated in python3.10. (see python/cpython#83710) ``` .../electrum/electrum/daemon.py:470: DeprecationWarning: There is no current event loop self.asyncio_loop = asyncio.get_event_loop() .../electrum/electrum/network.py:276: DeprecationWarning: There is no current event loop self.asyncio_loop = asyncio.get_event_loop() ``` Also, according to that thread, "set_event_loop() [... is] not deprecated by oversight". So, we stop using get_event_loop() and set_event_loop() in our own code. Note that libraries we use (such as the stdlib for python <3.10), might call get_event_loop, which then relies on us having called set_event_loop e.g. for the GUI thread. To work around this, a custom event loop policy providing a get_event_loop implementation is used. Previously, we have been using a single asyncio event loop, created with util.create_and_start_event_loop, and code in many places got a reference to this loop using asyncio.get_event_loop(). Now, we still use a single asyncio event loop, but it is now stored as a global in util._asyncio_event_loop (access with util.get_asyncio_loop()). I believe these changes also fix spesmilo#5376
asyncio.get_event_loop() became deprecated in python3.10. (see python/cpython#83710) ``` .../electrum/electrum/daemon.py:470: DeprecationWarning: There is no current event loop self.asyncio_loop = asyncio.get_event_loop() .../electrum/electrum/network.py:276: DeprecationWarning: There is no current event loop self.asyncio_loop = asyncio.get_event_loop() ``` Also, according to that thread, "set_event_loop() [... is] not deprecated by oversight". So, we stop using get_event_loop() and set_event_loop() in our own code. Note that libraries we use (such as the stdlib for python <3.10), might call get_event_loop, which then relies on us having called set_event_loop e.g. for the GUI thread. To work around this, a custom event loop policy providing a get_event_loop implementation is used. Previously, we have been using a single asyncio event loop, created with util.create_and_start_event_loop, and code in many places got a reference to this loop using asyncio.get_event_loop(). Now, we still use a single asyncio event loop, but it is now stored as a global in util._asyncio_event_loop (access with util.get_asyncio_loop()). I believe these changes also fix spesmilo#5376
get_event_loop was deprecated in 3.10, does this mean it will be an alias for get_running_loop in 3.12? |
It was not deprecated. Only its use outside of the event loop was deprecated. It will be an alias of |
@serhiy-storchaka Yes, on 3.12 will it be an alias of get_running_loop? |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: