gh-128384: Add thread-safe context manager to "warnings" module by nascheme · Pull Request #128300 · python/cpython (original) (raw)
This PR adds a new thread-local (and async friendly) context manager that uses contextvars
. The recommended pattern for new code that doesn't care about backwards compatibility would be:
with warnings.local_context() as ctx:
ctx.simplefilter(...)
...
For code that wants to be compatible with older versions of Python, the suggested code is:
import warnings
try:
warn_ctx = warnings.get_context
except AttributeError:
def warn_ctx():
return warnings
with warn_ctx().catch_warnings():
warn_ctx().simplefilter(...)
...
with warn_ctx().catch_warnings(record=True) as log:
...
This change retains warnings.filters
and warnings.catch_warnings()
as mechanisms that use process global state. Conceptually, there will be two sets of warning filters: the legacy process global warnings.filters
list and the thread local get_context()._filters
list. The context object returned by get_context()
has an API that overlaps with the warnings
module function API.
Perhaps we could eventually warn when the process global versions are used but I think that would be many years in the future and not worth considering now.
It would be intuitive if the thread local filtering was inherited when a new thread is created. I've created a separate PR that adds this feature to threading.Thread
: gh-128209. That's a potentially controversial change but I think it's the correct thing to do. It will make contextvars behave similarly between asyncio tasks (which already inherit context) and threads. I think people will expect the warnings context manager to have lexical scope. If you see the code:
with warnings.local_context() as ctx:
ctx.simplefilter(MyWarning)
inner_function()
Then you would assume that inner_function()
is going to have MyWarning
filtered, even if it internally spawns a thread.
Regarding backwards compatibility, I did a code search and there are quite a few examples for code that manipulates warnings.filters
directly, either mutating it or assigning a different list to the module global. There is also code that does this:
warnings.simplefilter(...)
...
warnings.filters.pop(0)
📚 Documentation preview 📚: https://cpython-previews--128300.org.readthedocs.build/