Why does a DeprecationWarning from an imported library not print by default?
answer
- The list is not empty at startup
- One audience is spared, another is not
- It depends which module gets the blame
- A special case for the top-level script
- default::DeprecationWarning via -W or PYTHONWARNINGS
basics
~10 sPython ships default entries in warnings.filters that ignore DeprecationWarning everywhere except when it is triggered from the main module. Surface them with -W default::DeprecationWarning, PYTHONWARNINGS, or a warnings.filterwarnings call.
solid answer
~40 sA fresh interpreter starts with a small default filter list that ignores `DeprecationWarning`, `PendingDeprecationWarning`, `ImportWarning` and `ResourceWarning`, plus one earlier entry that shows `DeprecationWarning` when the module it is attributed to is `__main__`. That split dates from Python 3.7 (PEP 565): end users of a script should not see library churn, but the author of the script should see their own. A warning emitted deep inside a library is attributed to that library's module, so the `ignore` entry wins and nothing prints. To see them, start the process with `-W default::DeprecationWarning` or `PYTHONWARNINGS=default::DeprecationWarning`, or call `warnings.filterwarnings("default", category=DeprecationWarning)` early - filters are matched in order, first match wins, and `filterwarnings` inserts at the front by default.
code
python · 7 linesimport warnings
for entry in warnings.filters:
print(entry)
warnings.filterwarnings("default", category=DeprecationWarning)
warnings.warn("legacy scorer", DeprecationWarning)go deeper
Know that some categories - DeprecationWarning above all - are hidden unless you ask for them, and that python -W default::DeprecationWarning is how you ask on a single run.
Be able to recite the shape of a filter entry, explain that the first match wins, and describe the __main__ special case that makes your own deprecations visible while a dependency's stay quiet.
Show that you make deprecations fatal where they can be acted on and quiet where they cannot, and that you set filters at interpreter start so import-time warnings are not missed.
Own the upgrade story: a stated policy for which categories fail a build, a reviewed list of pinned ignores rather than a blanket silence, and a rule for retiring each ignore.
### The default filter list A CPython 3.14 process starts with five entries in `warnings.filters`, in this order: ``` ('default', None, DeprecationWarning, '__main__', 0) ('ignore', None, DeprecationWarning, None, 0) ('ignore', None, PendingDeprecationWarning, None, 0) ('ignore', None, ImportWarning, None, 0) ('ignore', None, ResourceWarning, None, 0) ``` Each tuple is `(action, message-regex, category, module-regex, lineno)`. The list is scanned top to bottom and **the first match wins**; a warning that matches nothing gets the fallback action, which prints once per location. So `UserWarning` and `RuntimeWarning` are visible out of the box, while the four categories above are not - except for that first entry, which resurrects `DeprecationWarning` when the module it is attributed to is `__main__`. ### Why the split exists Before Python 3.7, `DeprecationWarning` was silenced unconditionally, and the result was that nobody saw deprecations until the removal release broke them. PEP 565 (Python 3.7) restored the middle ground now in place: a person *running* a program should not be shown that some dependency three layers down uses an old call - they cannot fix it and the noise is meaningless. A person *writing* the top-level script should be shown their own deprecated usage, because they can fix it right now. The module-name filter `__main__` is the mechanism that separates the two audiences. ### Why attribution is where the subtlety lives The module compared against `__main__` is not "the module that owns the deprecated function" - it is the module of the stack frame the warning was **attributed** to, which the emitter chooses through the `stacklevel` argument. A library that calls `warnings.warn(msg, DeprecationWarning)` with the default `stacklevel=1` attributes the warning to its own module, so the `ignore` entry matches and the user of that library never sees it, even from a script. The same library calling with `stacklevel=2` attributes it to the caller's frame; if the caller is the top-level script, the module is `__main__`, the first filter matches, and it prints. This is why a well-behaved library passes `stacklevel=2` on deprecations, and why two libraries can behave completely differently for the same category. ### Turning them on There are three lever positions, and which one you pick matters more than it looks. **At interpreter start, per run.** `-W default::DeprecationWarning` on the command line, or `PYTHONWARNINGS=default::DeprecationWarning` in the environment. The spec is `action:message:category:module:lineno`, each field optional, colons kept: `-W ignore:::third_party.legacy` silences one module, `-W error::DeprecationWarning` makes them fatal. Multiple `-W` options are allowed and later ones take precedence, and the raw strings stay visible in `sys.warnoptions`. This position is the only one that catches warnings emitted **during import**, before any of your code runs. **In code, coarse.** `warnings.simplefilter("default", DeprecationWarning)` throws away everything already configured for that category and inserts one entry. Convenient in a script; rude in a library, because it stomps on the application's policy. **In code, precise.** `warnings.filterwarnings(action, message=..., category=..., module=..., lineno=...)` inserts a fully specified entry at the **front** of the list, so the newest filter wins. The `message` and `module` values are regular expressions matched against the message text and the attributed module name - note that `module` is matched with `re.compile(module).match`, so anchor your patterns rather than assuming a substring test. `warnings.resetwarnings()` clears the list entirely, including the defaults, which is almost never what you want in application code. ### The practical failure mode A team upgrades a dependency, all tests pass, and a release later a function is gone. The deprecation had been firing for two minor versions into a filter that ignored it. The fix is policy, not cleverness: run the test suite with deprecations visible or fatal, so the signal arrives while there is still time to act, and keep the production process quieter. ### Related categories worth knowing `PendingDeprecationWarning` is the softer, earlier signal, still ignored by default and now rarely used - most projects go straight to `DeprecationWarning`. `FutureWarning` is *not* in the ignore list, and that is deliberate: it is the category for changes aimed at end users rather than at developers, so it is visible by default. `ResourceWarning` (unclosed files and sockets) is ignored by default because its attribution is unpredictable during garbage collection.
- What are the five fields of a -W filter spec, and what does an empty field mean?`action:message:category:module:lineno`. Empty fields are wildcards, so `-W error::DeprecationWarning` sets the action and category and matches any message, module and line. `message` and `module` are matched as regular expressions against the warning text and the attributed module name, and `lineno` defaults to 0, meaning any line. The colons must still be present as separators.
- Why is warnings.simplefilter a poor choice inside a library?It resets the filter list for that category and inserts a single entry, discarding whatever policy the application configured - including deliberate `ignore` rules and anything set through `-W`. A library should emit warnings and leave filtering to the application; if it must adjust filters temporarily, it should do so inside `warnings.catch_warnings()` so the change is restored.
- If two filters could both match a warning, which one applies?The first one in `warnings.filters`, scanned from index 0. `warnings.filterwarnings` and `warnings.simplefilter` insert at the front unless you pass `append=True`, so a filter added later normally wins over one added earlier - including over the startup defaults. Order, not specificity, decides.
saying these in an interview costs you the question
- Thinks warnings.filters starts empty in a fresh process
- Says DeprecationWarning is never shown by default anywhere
- Believes the most specific matching filter wins, not the first
- Confuses the category field with the module field in -W
- Calls warnings.resetwarnings in library code
- Assumes the warning is attributed to the library that owns the function