A traceback is the error report Python prints when something crashes. Reading them well is the first debugging skill.
We touched on tracebacks in Section 9 — this lesson goes deeper.
A simple traceback
def calculate(a: int, b: int) -> float:
return a / b
print(calculate(10, 0))
Traceback (most recent call last):
File "/path/to/main.py", line 5, in <module>
print(calculate(10, 0))
~~~~~~~~~^^^^^^^
File "/path/to/main.py", line 2, in calculate
return a / b
~~^~~
ZeroDivisionError: division by zero
Read from the bottom up:
ZeroDivisionError: division by zero— what went wrong.line 2, in calculate— where it went wrong.line 5, in <module>— who called the failing function.
The <module> label means “the top-level script”. For a real call stack with many layers, you’d see one frame for each.
Modern Python markup
Python 3.11 added the squiggly underlines you can see in the traceback:
return a / b
~~^~~
The ^ points at the exact part of the line that failed. For long expressions this is a big help — you no longer have to guess.
Common error types and what they usually mean
| Error | Typical cause |
|---|---|
NameError: name 'x' is not defined | Typo or missing import |
TypeError: unsupported operand | Wrong type for an operation ("hi" + 5) |
AttributeError: ... has no attribute 'foo' | Typo in attribute name |
KeyError: 'foo' | Missing dict key |
IndexError: list index out of range | List position too big or too small |
ValueError: invalid literal for int() | Bad string passed to a converter |
ZeroDivisionError | Divided by 0 |
FileNotFoundError | Wrong file path |
ImportError / ModuleNotFoundError | Wrong import name, missing package |
RecursionError | A function called itself too many times |
Python’s error messages are unusually friendly. Read them carefully — they usually tell you the answer.
Chained tracebacks — “The above exception was the direct cause”
When code catches one exception and raises a different one (Section 9), Python shows both:
ValueError: invalid literal for int() with base 10: 'abc'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 12, in <module>
load_port("abc")
File "main.py", line 7, in load_port
raise ConfigError(f"Port must be a number, got {text!r}") from e
ConfigError: Port must be a number, got 'abc'
Two errors, both shown. The original ValueError is the cause; the ConfigError is the one that ultimately propagated. Read from the bottom for what crashed; read from the top for why.
”During handling of the above exception, another exception occurred”
A different message:
The above exception was the direct cause of the following exception: # raise X from Y
During handling of the above exception, another exception occurred: # no `from`
The “during handling” form means a second exception was raised inside an except block, without from. The fix is usually to use from e to make the chain explicit:
try:
risky()
except ValueError as e:
raise RuntimeError("fallback") from e
Where the traceback was actually created
In a real program, the failing function might be deep in a third-party library. The traceback shows every layer:
File "/path/to/your_code.py", line 12, in main
File "/path/to/library/utils.py", line 88, in load
File "/path/to/library/internal.py", line 41, in _parse
ValueError: ...
Two strategies:
- Find your code in the trace. The deepest line that’s in your file is usually the most useful — that’s where you can change something.
- Read down the library frames. Sometimes the library is at fault. Reading its frame tells you what input it choked on.
Suppressing the chain — raise ... from None
If you specifically want to hide the original cause (rare):
try:
risky()
except ValueError as e:
raise ConfigError("oops") from None
This is occasionally useful when re-raising in a library where the internal cause would be confusing to callers. Use sparingly.
traceback module — capturing in code
You can capture and print tracebacks programmatically:
import traceback
try:
risky()
except Exception:
traceback.print_exc() # print to stderr
text = traceback.format_exc() # get as a string
log_to_file(text)
Useful when you want to log a traceback without crashing the program — common in web servers and long-running scripts.
What’s next
You can read what Python tells you when things fail. Next — using the debugger to pause and inspect the state of a running program.