Read a Python traceback and repair the failing assumption
Trace a KeyError through three function calls, identify the broken input contract and verify a repair without swallowing unrelated exceptions.
Read a Python traceback from the final exception message back through the calls that led to it. The last frame usually shows where the exception was raised. The earlier frames explain how the program reached that operation. Fix the assumption that failed, then rerun the original input and a boundary case.
Here a purchase summary expects amount, but the incoming record contains price. This is a schema mismatch, not evidence that dictionaries or function calls are unreliable. Our original fixture lets us reproduce the mistake without depending on a remote service.
Follow the calls into the failure
report calls subtotal, which calls read_amount. The failing expression is row['amount']. A KeyError identifies an unavailable mapping key; it does not mean the value was zero, blank or the wrong numeric type. Replacing every exception with zero would produce a plausible but incorrect total.
A normal traceback prints file paths, line numbers and source lines. This runnable example extracts the same frame names so the checked output stays stable when you save the code under a different filename. It also prints the actual exception type and missing key. In a real traceback, use the file and line information to open the exact failing expression.
import traceback
def read_amount(row):
return float(row['amount'])
def subtotal(rows):
return sum(read_amount(row) for row in rows)
def report(rows):
return subtotal(rows)
incoming = [{'item': 'kit', 'price': '12.50'}]
try:
report(incoming)
except KeyError as error:
frames = traceback.extract_tb(error.__traceback__)
print('calls:', ' -> '.join(frame.name for frame in frames[-4:]))
print(type(error).__name__, str(error))
# Adapt the documented external schema at the import boundary.
def adapt(row):
if set(row) != {'item', 'price'}:
raise ValueError('Expected item and price')
return {'item': row['item'], 'amount': row['price']}
print(f"repaired total={report([adapt(row) for row in incoming]):.2f}")
try:
adapt({'item': 'kit'})
except ValueError as error:
print('boundary:', str(error))
assert report([adapt(row) for row in incoming]) == 12.5
Checked output:
calls: report -> subtotal -> <genexpr> -> read_amount
KeyError 'amount'
repaired total=12.50
boundary: Expected item and price
Repair the contract at the right boundary
The adapter explicitly translates the external price field into the internal amount field. It also rejects an unexpected schema. This is appropriate if price really is the supplier's documented name. If the supplier promised amount, accepting price silently could conceal a broken integration; rejecting the record may be the better repair.
The conversion to float can still raise ValueError for a string such as twelve. That is a different failure with a different explanation. Test it separately instead of broadening the KeyError handler until every bad input becomes a zero. For exact currency arithmetic, use the decimal policy demonstrated in streaming CSV records.
Distinguish the traceback from the exception chain
A traceback describes the stack associated with one exception. An exception chain can show that one exception occurred while handling another, or that code deliberately raised a new exception from an underlying cause. Read each exception message and the sentence connecting them. The most recent message may be an application-level explanation of an earlier parser or network failure.
raise ValueError("Invalid purchase") from error can preserve a useful cause when translating low-level failures. Use it only when the new message adds meaning. Repeatedly catching and re-raising without context makes the diagnostic trail harder to follow.
Check the repair instead of hiding the symptom
Keep the failing record as a regression fixture. Verify that the repaired version returns the intended total, that a missing field is rejected, and that invalid numeric data produces a clear failure. Include the name of the field or safe record identifier in an application error; avoid exposing credentials or private payloads.
A syntax error may prevent a module from running at all and is presented differently from this runtime call stack. An error inside a library still requires inspecting your calling input; the last library frame is not automatically the location where your own assumptions first went wrong.
The Python course connects exceptions to functions and input handling. Compare this boundary repair with mutable default arguments, where the input can be valid but retained state causes the surprising result. Both debugging tasks begin with a reproducible example and an explicit expectation.
References
Python errors and exceptions — Tracebacks, exception handling and explicit exception chaining.