skip to content

Why is a DeprecationWarning raised inside an imported library invisible when you run a Python script?

level: juniorimportance: should knowfreq 32%

answer

  1. Warnings pass through a filter list
  2. The default list hides some categories
  3. One module is treated specially
  4. Command line and environment both set filters
  5. The error action fails instead of printing

basics

~10 s

CPython'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 s

By 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 line
console
python -c "import datetime; datetime.datetime.utcnow()"

go deeper

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context