PYTHON / GETTING STARTED
Reading a traceback
Read any Python traceback bottom-up: name the exception, map each frame to a call, and pinpoint the line and value that caused the failure.
What you will learn
- Read the final line first: it holds the exception type and the offending value
- Treat each 'File ..., line N, in name' block as one call that was still running
- Locate the deepest frame in your own code, then check the frame above it for the bad value
- Recognise the ^^^ and ~~~ markers as the exact sub-expression that raised
Understanding Reading a traceback
When an exception is never caught, the interpreter stops the program, prints a traceback to standard error, and exits with a non-zero status. The block opens with `Traceback (most recent call last):`, which is a literal statement about ordering: calls are listed oldest first, so the frame closest to the bottom is the one that was executing when the error happened. Underneath the frames sits the single most informative line: the exception type, a colon, and a message that usually quotes the value Python choked on. Start there, then work upward.
Each `File ..., line N, in name` pair plus the source line printed under it is one frame, meaning one function call that had started but not finished. In the main example `<module>` called `total`, `total` called `parse_price`, and `parse_price` handed `'free'` to `float`. This is why the frame where the error surfaced is often not the frame you edit: `float(raw)` is perfectly good code, and the real defect is the string that arrived from above. When library files show up at the bottom, scan upward to the deepest frame whose path is inside your own project, because that is where your assumption broke.
From Python 3.11 onward every frame also gets a marker line that narrows the failure to a sub-expression: `^^^` under the call, operator, or subscript that raised, and `~~~` under the object it was applied to. That is what tells you whether `a[k1][k2]` failed on the first lookup or the second. SyntaxError is the odd one out, since nothing ran: you get no frame list, just a file, a line, and a caret, and with an unclosed bracket the reported line can be several lines after the real typo. If you see two tracebacks joined by "During handling of the above exception, another exception occurred", the lower one is what actually stopped the program and the upper one is the original cause.
def parse_price(raw):
return float(raw)
def total(rows):
result = 0.0
for row in rows:
result += parse_price(row)
return result
print(total(["10.50", "3.25", "free"]))
A traceback is the call stack printed oldest call first, so you read the last line for what broke and then walk the frames upward to find where the bad value came from.
Worked examples
The last line: type and message
Shows that a traceback's final line is nothing more than the exception's class name plus its message text.
def check(fn, arg):
try:
fn(arg)
except Exception as err:
print(type(err).__name__ + ": " + str(err))
else:
print("ok:", arg)
check(float, "10.50")
check(float, "free")
check(len, 5)
check(int, "12abc")
Example explained
Line 1`except Exception as err` binds the exception object, and `type(err).__name__` is exactly the word printed before the colon in a real traceback.
Line 2`str(err)` is the text after the colon; for `float('free')` it repeats the rejected value in quotes, which is your search string.
Line 3`len(5)` raises TypeError, not ValueError: the type tells you the operation was wrong for that kind of object, not that the value was malformed.
Line 4`int('12abc')` mentions base 10, a reminder that messages are written by whoever raised them and vary in helpfulness.
The deepest frame is not always the bug
A missing dictionary key raises while arguments are still being evaluated, so the function that would have received them never appears in the traceback.
def apply_discount(price, percent):
return price * (1 - percent / 100)
def line_total(item):
return apply_discount(item["price"], item["discount"])
item = {"price": 15.0}
print(round(line_total(item), 2))
Example explained
Line 1`apply_discount` is absent from the frame list because the KeyError happened while building its arguments, so the call never started.
Line 2The `~~~~^^^^^^^^^^^^` line puts tildes under `item` and carets under `["discount"]`, proving the second lookup failed and not `item["price"]`.
Line 3The `<module>` frame underlines `line_total(item)` rather than `round(...)`, because that is the call the exception travelled out of.
Line 4The fix belongs where `item` was built on line 9, one frame above the frame that raised.
A traceback is data you can inspect
Walks the traceback object attached to a caught exception to print the same frames the interpreter would have shown.
import traceback
def parse_port(text):
return int(text)
try:
parse_port("8080a")
except ValueError as err:
for frame in traceback.extract_tb(err.__traceback__):
print(frame.lineno, frame.name, "|", frame.line)
print("last line ->", type(err).__name__ + ":", err)
Example explained
Line 1`err.__traceback__` is the same linked list of frames the interpreter would have printed, so nothing here is a reconstruction.
Line 2`extract_tb` returns the frames oldest first, which is why the printed order matches a real traceback and the last entry is the deepest call.
Line 3`frame.line` is read back from the source file, which is why a printed traceback can only show source that still exists on disk.
Line 4The final `print` rebuilds the traceback's closing line by hand: class name, colon, message.
Important notes
The paths in `File "..."` belong to the machine that ran the code, so yours will differ from any printed example; only the file name and line number matter for navigation.
The `^^^` and `~~~` marker lines were added in Python 3.11. On 3.10 and earlier the same traceback shows only the `File` lines, the source lines, and the final error line.
Common mistakes
Reading only the first `File` line (usually `in <module>`) and editing there; that frame is just where the call chain started, so you end up changing code that works.
Assuming the deepest frame contains the bug, then rewriting something like `float(raw)` or a stdlib call, when the wrong value was passed in by the caller above it.
Pasting only the last line when asking for help or saving a log, which discards the frame list that shows which call site produced the bad value.
Try it yourself
Change, predict, then run
Write three functions where the outer one passes data to the middle one, which passes a value to the innermost one that calls `int()` on it, and feed it the string "12 items" so it crashes. Without touching the innermost function, use the traceback's frame list to find and fix the line that supplied the bad value.
Open the Python workspaceCheck your understanding
A traceback ends with `ValueError: could not convert string to float: 'free'`, its frames are `<module>` then `total` then `parse_price`, and `parse_price` contains only `return float(raw)`. Where is the defect most likely to be?
- In the data or caller that passed 'free' down into parse_price
- In parse_price, because it is the frame printed last
- In float(), because it fails on some strings
- In the `<module>` frame's print() call, because it is printed first
Show answer
The bottom frame is where the exception surfaced, not necessarily where the mistake lives; `float(raw)` is correct code that was handed a value it cannot accept, so the fix belongs upstream where 'free' entered the list. Option 1 is tempting because 'most recent call last' does put the raising frame at the bottom, but that only tells you where execution was, not which line created the bad input.