When should a library call warnings.warn for a soft failure instead of raising an exception?
answer
- Advisory versus actionable
- Did the call deliver what it promised?
- Shown once per location per process
- Stderr, not the caller's logging
- warn may raise if filters say error
basics
~20 sWarn only when the call still delivered what it promised and the caller should change something next time. An incomplete or wrong result — a silently truncated document — is a failure: raise it, or state it in the return value.
solid answer
~50 s`warnings.warn` is advisory: by default the program keeps running, the message goes to stderr rather than to the caller's logging, and the same warning from the same line is shown only once per process. That makes it right for a deprecated-but-working call, and wrong for anything the caller must act on. A document-conversion library that truncates at fifty pages and merely warns has turned data loss into a stderr line a queue worker will never see — the warning fires once, on the first job, and every later truncation is silent. Signal that by raising, or by returning a result object that states what was dropped so the caller can branch on it. Pass `stacklevel=2` so the warning points at the caller's line, and never assume execution continues: a caller running with warnings as errors will get an exception from your `warn` call.
code
python · 19 linesimport warnings
def convert(pages, max_pages=50):
if len(pages) > max_pages:
warnings.warn(
f"only the first {max_pages} pages were converted",
RuntimeWarning,
stacklevel=2,
)
pages = pages[:max_pages]
return pages
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
convert(list(range(60)))
print(caught[0].category.__name__, "->", caught[0].message)go deeper
Learn the split: warn when the call still did its job but something should change; raise when the result is missing or incomplete. warnings.warn does not stop execution by default.
Explain the mechanics that make a warning weak — filtered by the caller, shown once per location, written to stderr rather than logging — and why stacklevel exists.
Diagnose the real failure: in a long-lived worker the truncation warning fires once and everything after it is invisible. Show the alternative designs, including returning a result object that carries what was dropped.
Own the policy: which conditions your libraries are allowed to warn about at all, how emitted warnings are documented as contract, and how teams reach warnings that never appear in a shipped log pipeline.
### The contract question, not the API question `warnings.warn(message, category, stacklevel=...)` is easy to call. The interview question is when a library is *entitled* to use it, and the answer follows from one distinction: - **Did the call deliver what its signature promised?** If yes, and there is something the caller ought to change, warn. - **If no** — the result is missing, partial, or subtly wrong — that is a failure. Raise, or return a value that makes the shortfall explicit. A warning is a message to a *human reading output later*. An exception is a signal to *code executing now*. Choosing the first when you needed the second is how silent data corruption ships. ### Why a warning is a weak channel Four properties make warnings unsuitable for real failures. **They are filtered, and not by you.** The caller's process decides what is shown, through the filter chain and `PYTHONWARNINGS`. Some categories are hidden by default outside `__main__`. Your library cannot know whether the message is displayed at all. **They fire once per location by default.** The default filter shows a given warning once per source location per process. In a long-lived worker draining a document-conversion queue, the first truncated job prints a line and every subsequent truncated job — for the life of the process — prints nothing. Anyone reasoning from the log concludes it happened once. **They go to stderr, not to logging.** Structured logs, log shipping, alerting and correlation IDs all live in `logging`. A warning bypasses all of it unless the application has called `logging.captureWarnings(True)`, which most have not. **They are not actionable in code.** A caller cannot branch on a warning without wrapping the call in `warnings.catch_warnings`, which is process-wide state and is documented as not thread-safe. Nobody does this in production code, so in practice a warning is unhandleable. ### The inverse trap: a warning can become an exception Because a caller may run with warnings promoted to errors — a very common CI setting — `warnings.warn` may not return at all. Library code must therefore never rely on execution continuing past a warn call, and must never leave an object half-mutated before one. Treat every `warn` as a possible raise point. That cuts both ways in the design: it is an argument against warning on a path where you were going to continue doing important work anyway, and it is a reminder that the "soft" in soft failure is entirely the caller's choice, not yours. ### What genuinely belongs in a warning - **Deprecation.** The call worked; the next release will remove it. This is the archetypal case and the one warnings were designed for. - **A configuration the caller should fix, where a sane default was applied and the result is still correct** — for example an unknown option key that was ignored, when ignoring it changes nothing about the output. - **A slow or fallback path taken**, where the result is identical but the caller may want to know they lost a fast path. Every one of those has the same shape: **the returned value is fully correct.** ### What does not Truncation, dropped records, a skipped page, a failed retry, an unparsed field replaced by a default, an unverified certificate. In each case the caller received something that does not match the contract and cannot tell from the return value. The right designs are: raise a package exception; or, when partial success is genuinely useful, change the return type so the shortfall is data — a result object carrying `converted_pages` and `dropped_pages`, which the caller must look at to use the result at all. "Return a result object" is often the better answer for batch APIs, because raising throws away the work that succeeded. ### Mechanics worth getting right when you do warn Pass `stacklevel=2` from a public function so the warning is attributed to the caller's line rather than to a line inside your package — with the default of 1 the user sees a file they cannot edit. If the public function is reached through a wrapper, the level goes up accordingly. Choose a category that matches the meaning rather than defaulting to `UserWarning` for everything. Never warn inside a tight loop: with the "always" filter active you have generated a million lines, and with the default filter you have generated one and hidden the rest. Finally, document it. A warning your library emits is part of the observable contract just as an exception type is — if it is worth emitting, it is worth a line in the docstring saying under what condition it appears.
- Why does stacklevel matter to the caller?With the default of 1 the warning is attributed to the line inside your library, which the user cannot change and did not write. `stacklevel=2` from a public function points at the caller's own call site, which is where the fix belongs. Add one more level for each internal wrapper the warning passes through.
- The library truncates a document at fifty pages. What is a better design than warning?Make the shortfall part of the value. Return a result object carrying both the converted pages and the count dropped, so the caller cannot use the output without seeing it. If partial success is not useful to anyone, raise instead. Either way the caller's code can react; a warning only reaches a human reading stderr.
- Can library code assume execution continues after warnings.warn returns?No. A caller can promote warnings to errors, which is a common continuous-integration setting, and then `warn` raises instead of returning. Treat every warn call as a possible raise point: do not leave state half-mutated before it and do not rely on cleanup that only happens after it.
A warning is a sticky note left on a desk; an exception is a phone call. You do not leave a sticky note to say the shipment went out half empty.
saying these in an interview costs you the question
- Warns on data loss instead of raising
- Assumes the caller will see every warning
- Thinks warnings go to the application's log by default
- Omits stacklevel so warnings blame the library's own file
- Assumes warn always returns normally
- Warns inside a loop for every item