Silent DeprecationWarning suppression in imported modules
0 reputation · 10 Jul 2025, 13:31 UTC
0 reputation · 10 Jul 2025, 13:31 UTC
Managing deprecation alerts in a large codebase without flooding logs depends on the warnings module’s ordered filter list and each module’s __warningregistry__. The documented default filters ignore DeprecationWarning outside __main__, so third-party deprecations can remain silent unless a filter explicitly opts in. Even after inserting an always action at runtime, the registry may still suppress a warning that was already recorded at a given module and line number, meaning catch_warnings(record=True) does not necessarily re-enable repeat emission within the same process.
The effective policy is order-sensitive: startup flags and environment variables are applied before runtime filterwarnings or simplefilter calls, which insert at the front of the list. Because default filter lists and registry semantics have shifted across versions, the exact behavior for a target interpreter is not always obvious.
In Python 3.11 and later, does bypassing or clearing __warningregistry__ inside catch_warnings(record=True) reliably re-emit a previously suppressed warning, or does registry state persist across the context? How do runtime filter insertions interact with startup -W and PYTHONWARNINGS entries to determine the first matching action for DeprecationWarning raised in an imported module? Is the documented default ignore of DeprecationWarning outside __main__ still authoritative in current releases, or has the default filter list changed?
34775 reputation · 10 Jul 2025, 20:40 UTC
In Python 3.11+, catch_warnings(record=True) does not clear the __warningregistry__. If a warning was previously suppressed or emitted at a specific module and line number, it is recorded in that module's registry. The context manager only captures warnings that are emitted during its execution; it cannot force the re-emission of a warning that the registry has already marked as handled. To reliably re-emit a previously suppressed warning, you must explicitly clear the __warningregistry__ of the target module.
The determination of the first matching action for a DeprecationWarning follows a strict precedence based on the order of the filter list:
warnings.filterwarnings() or warnings.simplefilter() insert new filters at the front (index 0) of the list. These take the highest precedence.-W flag or the PYTHONWARNINGS environment variable are applied during interpreter initialization and are appended to the list in the order they appear.DeprecationWarning is ignored unless it originates from __main__. This acts as the fallback if no other filters match.Because runtime insertions happen at the front, a runtime always filter will override a startup ignore filter, but a startup always filter will be overridden by a subsequent runtime ignore call.
To verify if a warning is being suppressed by the registry rather than the filter list, you can use the following scoped approach:
import warnings
import target_module
# 1. Trigger the warning once
target_module.trigger_deprecation()
# 2. Attempt to capture it again (will likely be empty)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
target_module.trigger_deprecation()
print(f"Captured: {len(w)}") # Likely 0
# 3. Clear registry and retry
target_module.__warningregistry__.clear()
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
target_module.trigger_deprecation()
print(f"Captured after clear: {len(w)}") # Likely 1
This behavior assumes the use of CPython. The __warningregistry__ is an implementation detail and is not guaranteed to persist across all Python implementations (e.g., PyPy) or future versions. If you are using a custom warning manager or a third-party logging wrapper that intercepts sys.warnoptions, the filter order may vary.
Diagnostic Detail Required: Are you observing this behavior in a multiprocessing environment or a long-running interactive shell (like IPython/Jupyter)? Registry persistence behaves differently across process boundaries.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 10 Jul 2025, 15:36 UTC
The context manager only captures warnings that are emitted while it is active. It does not reset the global __warningregistry__ or the filter list. A warning that was already recorded before the context will stay suppressed inside the block unless you explicitly clear the registry entry or call warnings.resetwarnings() first.
Startup flags (-W or PYTHONWARNINGS) populate the filter list first. Subsequent warnings.filterwarnings or simplefilter calls insert new rules at the front, so they win over the defaults. The first matching rule still determines the action, even inside a catch_warnings block.
Python 3.11 continues to ignore DeprecationWarning unless the warning originates from __main__ or an explicit filter changes the action. This policy has stayed unchanged in recent releases, so relying on it is safe as long as you don’t expect future changes.
In practice, if you need a previously suppressed warning to surface, do:
import warnings
warnings.resetwarnings() # or delete the specific registry entry
with warnings.catch_warnings(record=True) as w:
import mymodule