skip to content

What does traceback.format_exception() return when passed an exception object?

level: middleimportance: should knowfreq 44%

answer

  1. It takes the exception, not the current one
  2. The return value is not a string
  3. Join the pieces with the empty string
  4. One argument since a 3.1x release
  5. A default parameter controls the earlier exceptions

basics

~20 s

It returns a list of strings that concatenate to exactly the report the interpreter would print. Since Python 3.10 you pass just the exception instance; by default it also renders the exceptions reachable through cause and context.

solid answer

~40 s

`traceback.format_exception(exc)` renders any exception you are holding — not only the one currently being handled — and returns a **list of `str`**, so `"".join(...)` gives the finished report. Since **3.10** the single-argument form is the signature; the legacy three-argument `(type, value, tb)` call still works, and the type argument is ignored and inferred from the value. `chain` defaults to `True`, so the output walks `__cause__` and `__context__` and prints the earlier exceptions above, joined by the standard banner lines; `chain=False` renders this exception alone. `limit` bounds the frames rendered. If you want only the final `TypeError: ...` line without any stack, `traceback.format_exception_only(exc)` gives that. `traceback.print_exception` is the same renderer writing to a stream instead.

code

python · 17 lines
python
import traceback

def load(record):
    return record["timestamp"]

held = None
try:
    try:
        load({})
    except KeyError as exc:
        raise ValueError("record is missing a timestamp") from exc
except ValueError as exc:
    held = exc

print("".join(traceback.format_exception(held)))
print(traceback.format_exception_only(held))
print(len(traceback.format_exception(held, chain=False)), "chunks without the chain")

go deeper

for a junior

Remember the practical shape: this one takes the exception object and gives you back a list of strings you join with the empty string. It is the function to reach for when the failure is not the one you are currently handling.

for a middle

Explain the 3.10 single-argument signature, the list return value, and what chain=True adds to the output. Be able to say why it exists alongside format_exc rather than duplicating it.

for a senior

Show judgement about report content: what you keep versus suppress, when the chain is signal, and how you pair a one-line summary from format_exception_only with the full rendered text for grouping failures.

for a principal

Decide the house format for failure reports — what is stored, what is indexed, what is discarded — and make it uniform enough that failures from different services can be compared without a human reading every report.

### Why this function exists next to format_exc `traceback.format_exc()` can only render the exception currently being handled. `traceback.format_exception()` renders **an exception object you hand it**, which makes it the function you need once the failure has travelled: pulled off a queue, returned by `concurrent.futures.Future.exception()`, collected into a list during a batch run, or stashed on an object for a later report. ### The signature changed in 3.10 Historically the call was `format_exception(etype, value, tb)` — the three members of `sys.exc_info()`. Since **3.10** the exception instance alone is the intended positional argument, because an exception already carries its traceback on `__traceback__` and its class is its type. The old three-argument form is still accepted for compatibility, and in it the first argument is ignored and the type is inferred from the value. `format_exception_only` and `print_exception` gained the same single-argument form at the same time. If you are reading code that passes three arguments, it is either old or written by someone who learned the API before 3.10. ### The return value is a list, and that is deliberate You get a `list[str]`, not a string. Each element ends in a newline and some elements span several lines — the frame line plus its source line arrive together, for instance. Joining with the empty string, `"".join(format_exception(exc))`, produces exactly the text the interpreter would have written to `sys.stderr` for an unhandled exception. Joining with `"\n"` is a common bug: it doubles every line break. The list shape exists so a caller can stream the pieces to a writer without building the whole report in memory, and so tooling can post-process chunk by chunk. In application code you almost always join it immediately. ### Chaining in the output `chain=True` is the default. When set, the renderer follows the exception's `__cause__` first and its `__context__` otherwise, rendering the *earlier* exception above the later one and separating them with a fixed banner: an explicit `raise ... from ...` cause reads as a direct cause, while an exception raised incidentally inside a handler reads as having occurred during the handling of the previous one. `__suppress_context__`, which `raise ... from None` sets, tells the renderer to stop — that is how you deliberately hide an internal failure from the report. Passing `chain=False` renders only the exception you passed. That is occasionally what you want when the context chain is noise — a wrapper library that re-raises through three layers — but it is a lossy choice, and in a bug report the chain is usually the most valuable part of the text. Since **3.11**, an `ExceptionGroup` renders as an indented tree rather than a flat list, with each contained exception shown under its own branch. The same renderer handles it; you do not need a different call. ### format_exception_only `traceback.format_exception_only(exc)` returns just the type-and-message part — typically a single-element list like `["ValueError: bad record\n"]`, though a `SyntaxError` adds the offending source line and its marker. Use it for a one-line summary field alongside the full report: the summary is what you group failures by, the full text is what you read once you have picked a group. ### Choosing between the four renderers They are one machine with four front doors. `format_exc()` — current exception, one string. `format_exception(exc)` — any exception, list of strings. `print_exc()` and `print_exception(exc)` — the same two inputs, written to a stream, returning `None`. Everything else, `limit` and `chain`, is shared. Knowing which input each takes is the whole of the API, and getting it wrong is why people wrap `format_exc()` in a helper that mysteriously prints the wrong failure when called outside the handler. ### Summary line plus full report The pattern worth carrying away is to produce two things from one failure: the one-line summary from `format_exception_only()`, which is short enough to be a field you group and count on, and the joined output of `format_exception()`, which is the detail a human opens once. Storing only the full text forces anyone looking for patterns to parse it back; storing only the summary throws away the frames that tell you where to look. Both are cheap, and they answer different questions. ### What the renderer does not give you It gives you text and nothing else. There are no fields, no per-frame structure and no stable identity for the failure — two occurrences differing by one line number are two different strings. When you need frames as data rather than as a report, the traceback module has a separate extraction path for that, and mixing the two up — parsing the rendered text with a regular expression to recover file names — is the mistake this API exists to prevent.

  • Why does joining the result with a newline produce doubled blank lines?
    Every element of the returned list already ends in a newline, and some elements carry several lines of their own. Joining with the empty string reproduces the interpreter's exact output; joining with `"\n"` inserts a second break after each chunk, which is why the report comes out double-spaced.
  • When would you pass chain=False to traceback.format_exception?
    When the earlier exceptions are noise rather than signal — for example a wrapper that re-raises through several layers and whose intermediate failures add nothing. It is a lossy choice: the chain usually carries the original cause, so most reports are better off with the default.
  • What is traceback.format_exception_only useful for?
    It returns just the type-and-message portion with no frames, so it makes a good short summary field: group or deduplicate failures by that one line, and keep the full rendered report as the detail you open afterwards. For a SyntaxError it additionally includes the offending source line and its position marker.

saying these in an interview costs you the question

  • Says it returns a single ready-to-log string
  • Joins the returned list with a newline
  • Insists the three-argument call is still required
  • Thinks it only works on the current exception
  • Believes chained exceptions need a separate call

context