Why is a DeprecationWarning raised inside an imported library invisible when you run a Python script?
answer
- Warnings pass through a filter list
- The default list hides some categories
- One module is treated specially
- Command line and environment both set filters
- The error action fails instead of printing
basics
~10 sCPython's default warning filters ignore DeprecationWarning except when it is triggered in the __main__ module, so warnings from library code are silent. Surface them with -W default::DeprecationWarning, PYTHONWARNINGS, or python -X dev.
solid answer
~40 sBy default CPython shows `DeprecationWarning` only when it is attributed to `__main__` — the idea being that a deprecation is aimed at the developer writing the call, not at the end user of an application. Everything raised from an imported library is filtered out, and `PendingDeprecationWarning`, `ImportWarning` and `ResourceWarning` are ignored outright. You change this from outside the code: `python -W default::DeprecationWarning app.py` displays them, `PYTHONWARNINGS=default::DeprecationWarning` does the same through the environment and is inherited by child processes, and `python -X dev app.py` switches on the whole default filter along with the rest of development mode. To fail rather than print, use the `error` action: `-W error::DeprecationWarning`.
code
console · 1 linepython -c "import datetime; datetime.datetime.utcnow()"go deeper
Recall that Python hides some warning categories by default and that a deprecated call inside an imported module is silent. Know one way to see them: python -X dev or -W default::DeprecationWarning.
Explain the mechanism: an ordered filter list, DeprecationWarning shown only when attributed to __main__, and the action:message:category:module:lineno specification shared by -W and PYTHONWARNINGS. Know that default prints once per location.
Show the operational split: the error action in CI so deprecations fail a test, display-and-log in production, and the environment variable rather than a flag so spawned workers inherit it. Explain why a global error action in production is a bad trade.
Frame deprecations as upgrade telemetry: a continuous stream of one-line fixes now instead of a wall of breakage at the next interpreter upgrade, and a policy that scopes warnings-as-errors to code your teams can actually change.
**The default filters.** A warning is not an exception you catch; it is a message that passes through a list of filters, each deciding whether that category, from that module, is printed, ignored, or raised as an error. CPython ships a default list, and its shape is a deliberate product decision: warnings that are addressed to *developers* should not reach the *users* of an application. So `DeprecationWarning` is displayed only when it is triggered in `__main__`, and `PendingDeprecationWarning`, `ImportWarning` and `ResourceWarning` are ignored everywhere. That produces the surprise. If you call a deprecated function directly in the script you launched, you see the warning, because your call is attributed to `__main__`. Move the identical call one level down into a module you import — which is where the code lives in any real project — and it goes silent. The warning is still being raised on every call; nothing is displaying it. **Attribution, not location of the `warn()` call.** The module the filter matches against is the *caller's*, not the library's own module, because deprecation warnings are raised with a stack level pointing at the code that made the deprecated call. That is what makes the `__main__` rule useful at all: it means "warn the person whose line of code is at fault, when that person is obviously the one running this script". **How you turn them on.** Three controls, none of which needs a code change: - `-W` on the command line takes a filter specification `action:message:category:module:lineno`, and the parts you almost always use are the first three: `python -W default::DeprecationWarning app.py`. The actions worth remembering are `default` (print once per source location), `always`, `once`, `ignore` and `error`. - `PYTHONWARNINGS` takes the same specifications, comma-separated. Prefer it when the process you care about is not the one you launch — a worker spawned by a pool, a subprocess, a container entrypoint — because the environment is inherited and a command-line flag is not. - `python -X dev` (or `PYTHONDEVMODE=1`) applies the default filter as part of development mode, which is the usual way to get *all* the hidden categories at once, `ResourceWarning` included, rather than naming each. Later filters added at runtime take precedence over the command-line ones, so a library that installs its own filters can still silence what you asked to see; the `-W` and `PYTHONWARNINGS` settings are the baseline, not a guarantee. **Print or fail?** `default` prints once per code location, which is right for local work and for a service log you actually read. `error` raises the warning as an exception, which is what you want in CI: a deprecated call then fails a test instead of scrolling past in output nobody reads. The reason not to switch `error` on globally in production is equally practical — a deprecation inside a third-party dependency you cannot fix would crash your process for a message. The usual compromise is `error` in CI, narrowed to your own code, and `default` (displayed and logged) elsewhere. **Why it matters beyond tidiness.** Deprecation warnings are the early-warning system for interpreter and dependency upgrades. Each one is a line of your code that a future release intends to break, delivered a version or more ahead of time. A project that never displays them experiences every upgrade as a pile of simultaneous, undiagnosed failures; a project that runs its test suite with the filter on treats them as a small, continuous stream of one-line fixes. That is why "run CI in development mode" is a common, cheap policy — the mode's warning filter is doing most of the work. **Related gotcha: the once-per-location rule.** With the `default` action, the same warning from the same line prints once per process, so a warning you saw in one run may look absent in the next because a different code path reached it first. Use the `always` action when you are counting occurrences, and remember that a suite that imports modules in a different order can display a different set of warnings without any behaviour having changed.
- What is the difference between `-X dev` and `-W error::DeprecationWarning` for this problem?`-X dev` applies the default filter, so every hidden category — deprecations, `ImportWarning`, `ResourceWarning` — becomes visible, and it also brings the allocator hooks and asyncio debug mode along. `-W error::DeprecationWarning` targets one category and makes it raise instead of print. They compose: development mode for the broad sweep during a run, the `error` action in CI so a deprecated call fails a test rather than being printed and ignored.
- Why prefer PYTHONWARNINGS over the `-W` flag in a containerised service?Because a command-line option applies only to the process you launch, while the environment is inherited. An entrypoint that execs a supervisor, a pool that spawns workers, or a subprocess call would each drop a `-W` flag but keep `PYTHONWARNINGS`. It also keeps the setting in the same place as the rest of the deployment configuration, so it can differ per environment without editing a command line.
- Would you set warnings to error in production?No. A deprecation inside a dependency you do not control would then crash a running service over an advisory message. Display and log them in production if you want the signal, and reserve the `error` action for CI, ideally narrowed to warnings attributed to your own modules so a third-party deprecation cannot red the build for something you cannot fix.
saying these in an interview costs you the question
- Believing Python never raises deprecation warnings any more
- Thinking a try/except block can catch a displayed warning
- Assuming warnings are always printed on every occurrence
- Setting warnings to error globally in production
- Editing library code instead of setting a filter from outside