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:

  1. ZeroDivisionError: division by zero — what went wrong.
  2. line 2, in calculate — where it went wrong.
  3. 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

ErrorTypical cause
NameError: name 'x' is not definedTypo or missing import
TypeError: unsupported operandWrong 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 rangeList position too big or too small
ValueError: invalid literal for int()Bad string passed to a converter
ZeroDivisionErrorDivided by 0
FileNotFoundErrorWrong file path
ImportError / ModuleNotFoundErrorWrong import name, missing package
RecursionErrorA 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.

Toggle theme (T)