Python Quest

Track 9 · Lesson 1 of 11

Reading tracebacks

Read a Python traceback from the bottom up to find what went wrong, where, and how the program got there.

About 30 minBeginner4 exercisesFree

You will be able to

  • Name the parts of a traceback: header, frames, code lines, markers and the final error line
  • Read a traceback bottom-up to find the exception type and message first
  • Find the frame that points at your own code and the value that caused the problem
  • Follow a chain of function calls through the frames to see how the program reached the error

You run an order script against this morning's export and, instead of a total, you get eleven lines of red text. It is tempting to scroll past it and start changing code at random. Don't. That red text is a traceback, and it is the most precise bug report you will ever receive: it names the kind of problem, the value involved, the exact line, and every function call that led there.

When Python meets an operation it cannot carry out — looking up a key that does not exist, dividing by zero, adding text to a number — it creates an exception: an object describing the problem. If nothing in the program handles that exception, Python stops the program and prints the traceback. In this track you will learn to handle exceptions deliberately. First you need to read them.

An order total that crashes on an unknown product code
prices = {"SKU-101": 12.5, "SKU-205": 4.75}

def line_total(sku, qty):
    unit = prices[sku]
    return unit * qty

def order_total(lines):
    total = 0
    for sku, qty in lines:
        total += line_total(sku, qty)
    return total

print(order_total([("SKU-101", 2), ("SKU-309", 1)]))

Try it: Add "SKU-309": 7.0 to the prices dictionary and run it again. The traceback disappears and the total prints.

The traceback this prints looks like this:

Traceback (most recent call last):
  File "main.py", line 13, in <module>
    print(order_total([("SKU-101", 2), ("SKU-309", 1)]))
          ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "main.py", line 10, in order_total
    total += line_total(sku, qty)
             ~~~~~~~~~~^^^^^^^^^^
  File "main.py", line 4, in line_total
    unit = prices[sku]
           ~~~~~~^^^^^
KeyError: 'SKU-309'

Its parts, from top to bottom:

PartExampleMeaning
HeaderTraceback (most recent call last):The frames below are listed oldest first, newest last.
FrameFile "main.py", line 10, in order_totalOne function call that was still running: the file, the line number and the function's name. <module> means the top level of the file, outside any function.
Code linetotal += line_total(sku, qty)The source line Python was running in that frame.
Markers~~~~~~^^^^^Python 3.11+ underlines the exact expression; ^ marks the part that failed.
Error lineKeyError: 'SKU-309'The exception type, a colon, then the message.

Read it from the bottom up

The header says "most recent call last", so the most useful information is at the bottom:

  1. Last line first. KeyError: 'SKU-309' — a dictionary lookup failed, and the missing key was 'SKU-309'. Often this alone tells you the bug.
  2. The last frame. line 4, in line_total — the line that actually failed is unit = prices[sku].
  3. Work upwards to see how you got there: line_total was called by order_total on line 10, which was called from the top of the file on line 13.

The bug is rarely "in" the top frame. Line 13 is only where the chain of calls started. The fix here belongs either in the data (add the missing price) or in line_total (decide what an unknown product should do — a question this track answers in later lessons).

A message that names the wrong type
def format_line(name, qty):
    return name + " x" + qty

def print_receipt(items):
    for name, qty in items:
        print(format_line(name, qty))

print_receipt([("Kiwi", 3), ("Dates", 1)])

Try it: Read the last line of the traceback, then change qty to str(qty) inside format_line and run it again.

The last line here is TypeError: can only concatenate str (not "int") to str. Read the message literally: something tried to join a string with an integer using +. The last frame points at return name + " x" + qty in format_line, and the ^ marker sits under the second +. The value of qty came from print_receipt, one frame up, which got it from the list on the last line of the program.

Notice what did not happen: Kiwi was never printed. The exception stopped the program in the middle of the first loop iteration, before print() received anything.

A traceback has four frames: <module> line 30, load_report line 18, parse_row line 9, and finally a frame inside csv.py. The last line is ValueError. Where should you look first in your own code?

Guided practice · ●difficulty 1 of 5 · about 4 min

Name the culprit

An attendance script crashed. Here is the program, with line numbers:

 1  attendance = {"Lina": [1, 1, 0], "Omar": [1, 0, 1]}
 2
 3  def rate(days):
 4      return sum(days) / len(days)
 5
 6  def report(names):
 7      for name in names:
 8          print(name, rate(attendance[name]))
 9
10  report(["Lina", "Omar", "Yusuf"])

and the traceback it printed:

Traceback (most recent call last):
  File "main.py", line 10, in <module>
    report(["Lina", "Omar", "Yusuf"])
    ~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "main.py", line 8, in report
    print(name, rate(attendance[name]))
                     ~~~~~~~~~~^^^^^^
KeyError: 'Yusuf'

Read it and record what you found in four variables:

  • error_type — the exception type, as a string (for example "ValueError")
  • error_line — the line number (an int) where the error actually happened
  • error_function — the name of the function that line belongs to, as a string
  • bad_value — the value that caused the problem, as a string without extra quotes

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Debugging · ●●difficulty 2 of 5 · about 6 min

Fix the receipt line

A shop's receipt printer stopped working. Calling receipt([("Kiwi", 3, 4.5)]) produces:

Traceback (most recent call last):
  File "main.py", line 10, in <module>
    receipt([("Kiwi", 3, 4.5)])
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "main.py", line 8, in receipt
    lines.append(format_line(name, qty, price))
                 ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "main.py", line 3, in format_line
    return name + " x" + qty + " = " + f"{total:.2f}" + " SAR"
           ~~~~~~~~~~~~^~~~~
TypeError: can only concatenate str (not "int") to str

Read the traceback, find the line that failed and fix it. When it works:

  • format_line("Kiwi", 3, 4.5) returns "Kiwi x3 = 13.50 SAR" (total with exactly two decimals)
  • receipt(items) returns a list with one formatted line per item, and [] for an empty order.

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Challenge · ●●●difficulty 3 of 5 · about 12 min

Pull the facts out of a traceback

A support team pastes tracebacks from customers' crash reports into a ticket system. Write a function last_frame(tb) that takes the traceback text and returns a dictionary with the four facts you read first:

  • "error" — the exception type from the final line
  • "message" — everything after the first ": " on the final line (an empty string if there is no message)
  • "line" — the line number of the last frame, as an int
  • "function" — the function name of the last frame

Frame lines look like File "main.py", line 4, in line_total. Example: for the order traceback from this lesson, last_frame(tb) returns

{"error": "KeyError", "message": "'SKU-309'", "line": 4, "function": "line_total"}

Ignore blank lines, including blank lines at the end of the text. Messages can themselves contain ": ", so split only once.

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Edge cases · ●●difficulty 2 of 5 · about 6 min

The latest sensor reading

A greenhouse dashboard shows the latest temperature from each sensor. It crashes for every sensor:

Traceback (most recent call last):
  File "main.py", line 12, in <module>
    print(latest_summary("Greenhouse A", [21.5, 22.0, 22.4]))
          ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "main.py", line 6, in latest_summary
    value = latest_reading(readings)
  File "main.py", line 2, in latest_reading
    return readings[len(readings)]
           ~~~~~~~~^^^^^^^^^^^^^^^
IndexError: list index out of range

Fix latest_reading(readings) so it returns the last reading in the list. A sensor that has not reported yet has an empty list: then latest_reading must return None, so that latest_summary can show "Greenhouse A: no data".

Python code editor. Tab indents. Press Escape then Tab to leave the editor. Control or Command plus Enter runs the code; Shift plus Enter checks your answer.

Ctrl+Enter run · Shift+Enter check · your code is saved in this browser as you type

Output

Run your code to see its output, or Check to test it against the requirements.

The solution becomes available after you pass, or after all three hints and two checks.

Summary

  • An unhandled exception stops the program and prints a traceback: frames oldest first, the error last.
  • Read bottom-up: the final line gives the exception type and message; the last frame gives the failing line.
  • Each frame names a file, a line number and a function; <module> means the top level of the file.
  • Walk up the frames to see how the bad value reached the failing line; stop at the first frame in your own code.