skip to content

What does traceback.extract_tb() return, and when do you want it over rendered text?

level: middleimportance: should knowfreq 36%

answer

  1. Frames as data, not as text
  2. A list subclass of per-frame snapshots
  3. Four fields you can log separately
  4. The last element is where it broke
  5. Source text read from a cache

basics

~20 s

traceback.extract_tb() turns a traceback object into a StackSummary: a list of FrameSummary items carrying filename, lineno, name and the source line. You want it when the failure must become structured fields rather than one blob of text.

solid answer

~40 s

`traceback.extract_tb(exc.__traceback__)` returns a **`StackSummary`**, which is a `list` subclass whose elements are `FrameSummary` objects exposing `filename`, `lineno`, `name` and `line`. The order matches the printed report: outermost call first, the frame where the failure happened last. Because it is data rather than text, you can emit each frame as JSON fields in a log record, drop framework frames and keep only your own package, or group failures by the innermost frame so a thousand occurrences collapse into one entry. `StackSummary.format()` renders the list back into the familiar `File "...", line N, in f` lines, so you can filter first and print afterwards. Note that `line` is fetched from the source file through the `linecache` module, so it can be absent when the code came from a REPL or a file that has since changed.

code

python · 17 lines
python
import json
import traceback

def ingest(batch):
    parse(batch)

def parse(batch):
    raise RuntimeError("timestamp precedes previous batch")

try:
    ingest("2026-09-04")
except RuntimeError as exc:
    frames = traceback.extract_tb(exc.__traceback__)
    fields = [{"file": f.filename, "line": f.lineno, "func": f.name} for f in frames]
    print(json.dumps(fields))
    print("innermost frame:", frames[-1].name)
    print("".join(frames.format()), end="")

go deeper

for a junior

Know that a traceback can be turned into a list of per-frame records with a filename, a line number and a function name, and that the last record is where the failure happened. That is enough at this level.

for a middle

Explain what a FrameSummary holds, the ordering of the list, and why StackSummary.format rebuilds only the frame lines. Be ready to say when data beats text.

for a senior

Show how you turn failures into fields a log system can index and deduplicate: filtering out framework frames, fingerprinting on the innermost frames, and extracting early because the source line is read from the file at extraction time.

for a principal

Own the failure taxonomy across services: what a fingerprint is built from, how noisy repeats collapse, and how much per-frame detail is worth storing given retention costs and the risk of leaking source into logs.

### From a traceback object to data A traceback object is a linked list of frames, and it is awkward to work with directly. `traceback.extract_tb(tb)` walks it once and gives you a `StackSummary`. The input is the traceback: `exc.__traceback__` if you are holding the exception, or the third member of `sys.exc_info()` inside a handler. There is a sibling, `traceback.extract_stack()`, that does the same for the *current* call stack with no exception involved — useful for answering "who called me?" in a diagnostic hook. ### What a FrameSummary carries Each element is a `FrameSummary` with four fields you will actually use: - `filename` — the source file as recorded in the code object. - `lineno` — the line executing in that frame. - `name` — the function or method name. - `line` — the stripped source text of that line. The critical property is what a `FrameSummary` does **not** hold: the live frame. It is a flat snapshot, so keeping one does not pin the frame's local variables and everything they reference in memory. That is the difference between a summary you can safely put in a list and a raw traceback you cannot. `line` is not stored in the traceback — it is looked up from the file via the `linecache` module at extraction time. So it is empty for code that was `exec`'d or typed at the REPL, and it is *wrong* rather than missing if the file changed between the failure and the extraction. That is the main reason to extract early rather than late. ### Ordering A `StackSummary` is ordered oldest call first, innermost last — the same order the printed report uses. So `frames[-1]` is where the exception was actually raised and `frames[0]` is your entry point. Grouping failures by `frames[-1].filename` and `frames[-1].name` is the cheapest useful fingerprint you can compute, and it is why the summary form matters: you cannot group on a blob of rendered text without parsing it back. ### Why you would prefer this to rendered text In a pipeline that ingests logs, a traceback rendered as one multi-line string is close to useless as data. It breaks line-oriented log shipping, it cannot be indexed, and two identical failures with different line numbers in a vendored dependency look like different strings. Extracting the frames instead lets you: - emit `file`, `line` and `func` as separate structured fields, one object per frame; - filter frames whose `filename` sits outside your own package, so a five-frame report is not buried under thirty frames of framework plumbing; - compute a stable fingerprint from the innermost frames for deduplication; - keep the full report as a secondary field for a human, generated with `StackSummary.format()` after the filtering. A worked case: an overnight batch of a log-ingest pipeline raises hundreds of failures across a 27-minute run, most of them the same clock-skew artefact hitting the same parsing frame. Rendered as text they are hundreds of distinct log entries; extracted and fingerprinted by the innermost frame, they collapse into one entry with a count — and the two genuinely different failures underneath finally become visible. ### Rendering back `StackSummary.format()` returns a list of strings for the frames, matching the frame section of an ordinary report, and `StackSummary.from_list()` builds one from plain tuples if you are reconstructing frames received over the wire. Note that a `StackSummary` covers the **frames only** — it has no exception type or message, so a report built from one must add that line itself, typically from `traceback.format_exception_only()`. ### The lower-level door `traceback.walk_tb(tb)` yields `(frame, lineno)` pairs, and `StackSummary.extract()` consumes such an iterator — `extract_tb` is essentially those two composed. Reach for the pair when you need to inspect the live frames themselves before they are summarised; use `extract_tb` for everything else. ### The current stack, without an exception `traceback.extract_stack()` returns the same kind of `StackSummary` for the call stack executing right now, no exception required. It is how a diagnostic helper answers "who called me?" — a deprecation shim recording its callers, or an audit hook noting where a sensitive call originated. The caveat is cost and size: the current stack in a framework can be dozens of frames deep, each one triggering a source lookup, so it is a debugging and instrumentation tool rather than something to call on a hot path. ### Keep the summary, drop the traceback One more reason to extract rather than retain: a `StackSummary` is plain data. If you need to remember where something failed — for the rest of a batch, or long enough to write a report — the summary is safe to keep, while holding the traceback object keeps the live frames and their locals alive with it.

  • In the returned StackSummary, which end is the frame that raised the exception?
    The last element. The summary is ordered oldest call first, innermost last, matching the printed report, so `frames[-1]` is where the exception was raised and `frames[0]` is the outermost entry point. That last frame's filename and function name make the cheapest useful fingerprint for grouping repeated failures.
  • Why can a FrameSummary's source line be missing or wrong?
    The line text is not stored in the traceback; it is read from the source file through the linecache module when the summary is extracted. Code from the REPL or from an exec'd string has no file to read, and a file edited between the failure and the extraction yields the *new* text for the old line number.
  • How do you get the exception type and message when you only extracted the frames?
    You add it yourself — a StackSummary describes frames only and carries no exception at all. `traceback.format_exception_only(exc)` gives the type-and-message line, which you append to the frame lines from `StackSummary.format()` to rebuild a complete report.

saying these in an interview costs you the question

  • Says extract_tb returns the rendered traceback text
  • Thinks the first element is the failing frame
  • Believes a FrameSummary keeps the frame's locals
  • Assumes the source line lives in the traceback object
  • Expects the summary to include the exception message

context