{ } Programming

Reading a Python stack trace: a line-by-line guide

How to read a Python traceback: call order, exception type, chained exceptions and the most common errors, with examples explained line by line.

For people starting to program, a stack trace is a wall of red text announcing failure. For experienced developers, it is one of the most useful pieces of information a program can give: a precise map of where things went wrong and the path taken to get there. Learning to read it methodically saves an enormous amount of time.

What a stack trace is

When a program calls a function, which in turn calls another, the interpreter keeps track of this chain in a structure called the call stack. If an exception occurs that nobody handles, Python prints the contents of the stack at that moment: this is the traceback, Python's name for a stack trace.

In practice, the traceback answers three questions:

  1. what happened (exception type and message);
  2. where it happened (file, line, function);
  3. how it got there (the sequence of calls).

An annotated example

Consider this program:

def load_orders(lines):
    return [parse_line(l) for l in lines]

def parse_line(line):
    code, quantity = line.split(";")
    return code, int(quantity)

load_orders(["A12;3", "B07;two"])

Running it produces:

Traceback (most recent call last):
  File "orders.py", line 8, in <module>
    load_orders(["A12;3", "B07;two"])
  File "orders.py", line 2, in load_orders
    return [parse_line(l) for l in lines]
  File "orders.py", line 6, in parse_line
    return code, int(quantity)
ValueError: invalid literal for int() with base 10: 'two'

The first line, "most recent call last", is the key to reading it: calls are listed from oldest to most recent. In Python, therefore, the most important part is at the bottom.

How to read it, in the right order

  1. Start from the last line. ValueError: invalid literal for int() with base 10: 'two' says there was an attempt to convert the string 'two' to an integer.
  2. Go up to the most recent frame. Line 6 of parse_line: that is where the error surfaced.
  3. Keep going up to understand the context: parse_line was called by load_orders, which was in turn called by the main code on line 8.
  4. Ask yourself where the problem really comes from. It does not always coincide with the line of the error. Here the code is correct: it is the data "B07;two" that does not match the expected format. The right fix might be validating the input, not changing the conversion.

Your code and library code

In real projects, tracebacks often include dozens of frames from external libraries or the standard library. A practical trick is to look for the frames that belong to your files. The error may surface deep inside a library, but the cause is almost always a wrong argument passed by your code. The lowest frame among those from your project is usually the place to start.

Chained exceptions

Sometimes the traceback contains two blocks separated by a sentence. The two variants mean different things:

Sentence in the traceback Meaning
"During handling of the above exception, another exception occurred" While one exception was being handled, another occurred, often in the except block itself
"The above exception was the direct cause of the following exception" An exception was explicitly re-raised with raise ... from ...

In both cases it is worth reading all the blocks: the first often contains the original cause, the second how the program reacted.

The most common exceptions

Recognising the most frequent types at a glance speeds up diagnosis a lot:

  • NameError: an undefined name, often a typo or a variable used before being assigned;
  • TypeError: an operation on an unsuitable type, such as adding a string and a number, or a function called with the wrong arguments;
  • AttributeError: a non-existent attribute; the classic case is 'NoneType' object has no attribute ..., which indicates a function that returned None when an object was expected;
  • KeyError: a key missing from a dictionary;
  • IndexError: an index beyond the end of a list;
  • ValueError: a correct type but an unacceptable value, as in the example above.

Recent versions of Python have greatly improved their messages, adding hints such as "Did you mean...?" and markers underlining the exact part of the expression that caused the error. They are worth reading carefully.

Tracebacks in logs

In production nobody sees the terminal: the traceback must end up in the logs. The logging module records it automatically if, inside an except block, you use logger.exception("message"). A log message without a traceback, along the lines of "Error during processing", forces you to guess; one with the full traceback points straight to the place to examine.

In short

  • In Python a stack trace is read from the bottom: the last line gives the exception type and message.
  • Going up through the frames reconstructs the path that led to the error.
  • The line where the error surfaces is not always where the problem originates.
  • Look for your own code's frames among library frames and read every block of chained exceptions.
  • In production, always record the full traceback in the logs with logger.exception.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. With degrees in computer science and psychology, he combines technical analysis with an understanding of the human factor.

Read also