runtime
117 / 250
lesson 117 Debugging & Logging · intermediate

Find a bug: reproduce, isolate, inspect

A wrong answer gives no traceback. Make the bug happen on a small input, narrow it down to one function, then look at the values while it runs, with labelled prints or with the pdb debugger.

You'll be able to
  • Shrink a failing case to the smallest input that still shows the bug
  • Inspect values with labelled prints and repr, so text and numbers can't be confused
  • Read a pdb session: where it stopped, p to print a value, n for the next line, c to continue
The idea

An invoice total comes out too low and nothing crashes. Staring at the code rarely finds this kind of bug; a method does:

  1. Reproduce: find an input that shows the bug every time, and make it as small as you can. Three lines whose right total you can work out by hand beat a file of ten thousand.
  2. Isolate: check the pieces separately. If each line's total is right, the bug is in how they're combined.
  3. Inspect: print the values the code is actually working with, with labels, at the step you suspect.

The program below is genuinely broken. Run it before reading the note.

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · An order total that is too low
def line_total(line):
    price = line["unit_price"] * line["quantity"]
    if line["discount"]:
        price = price - price * line["discount"]
    return price

def order_total(lines):
    total = 0
    for line in lines:
        total = line_total(line)
    return total

lines = [
    {"sku": "21329", "unit_price": 2.5, "quantity": 4, "discount": 0},
    {"sku": "85048", "unit_price": 10.0, "quantity": 1, "discount": 0.5},
    {"sku": "21252", "unit_price": 1.0, "quantity": 3, "discount": 0},
]
print("order total:", order_total(lines), "expected:", 10 + 5 + 3)
for line in lines:
    print("isolate:", line["sku"], line_total(line))
total = 0
for line in lines:
    total = line_total(line)
    print("inspect: after", line["sku"], "total is", total)

Prints have to be added and removed by hand. A debugger lets you pause a running program and ask questions. Python's is pdb. Put breakpoint() on a line (or import pdb; pdb.set_trace(), which is the same) and run the script in a terminal: it stops there and shows a (Pdb) prompt. The most used commands are p name (print a value), n (run the next line), s (step into a call), l (list the code around you) and c (continue running).

This page's console can't take typed debugger commands, so the next example gives pdb its commands as text instead: a real pdb session, with the commands p total, count, p ratings and c already written down. In a terminal you would type them at the prompt.

example-2.py · A real pdb session on an average that is too high
import io
import os
import pdb

commands = io.StringIO("p total, count\np ratings\nc\n")
session = io.StringIO()
debugger = pdb.Pdb(stdin=commands, stdout=session)

def average_rating(ratings):
    total = 0
    for rating in ratings:
        total += rating
    count = len(ratings) - 1
    debugger.set_trace()
    return total / count

print("average:", average_rating([4, 5, 3]))
print(session.getvalue().replace(os.getcwd() + "/", ""))

When to use it: whenever the result is wrong but nothing crashes, or a crash's cause is far from where it happened. Prints are quick for a loop you suspect; pdb is better when you don't yet know which value to look at.

Mistakes to avoid: debugging on the full data set (shrink it first); changing several things at once and not knowing which one fixed it; printing values without labels; and plain print(value) when the type matters: print("12") and print(12) look the same, print(repr(value)) doesn't. Remove debugging prints and breakpoints once the bug is fixed.

Review
  • Reproduce on a small input with a known answer, isolate the function, then inspect the values.
  • Label your prints, and use repr when the type matters.
  • breakpoint() stops in pdb: p prints, n runs the next line, s steps in, c continues.
  • Next: replacing debugging prints with logging that can stay in the program.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Find a bug: reproduce, isolate, inspect — Debugging & Logging · Runtime