Lesson 22 of 34 · 0 of 2 exercises solved

Debugging & Reading Tracebacks

Programs fail. The skill that separates beginners from professionals is not avoiding but reading them calmly and finding the cause fast.

Read a traceback from the bottom up. The last line names the exception and its message. Above it, each "File ... line ..., in ..." entry is a frame; the lowest frame is where the error happened, the ones above are who called it. Run this and read the output before reading on.

def average(numbers):
    return sum(numbers) / len(numbers)

def report(groups):
    for name, scores in groups.items():
        print(name, average(scores))

report({"math": [90, 85], "art": []})
Python (loads on first run)Ctrl+Enter runs

The most common exceptions tell you exactly what to look for. NameError: a typo or a used before assignment. TypeError: wrong kind of value, often where an object was expected. KeyError/IndexError: a lookup that does not exist. AttributeError: usually None again, or a misspelled . ValueError: right type, wrong content.

# Each of these raises a different exception. Run it, then fix them one by one.
samples = [
    lambda: undefined_name,
    lambda: "3" + 3,
    lambda: {"a": 1}["b"],
    lambda: [1, 2, 3][5],
    lambda: None.upper(),
    lambda: int("forty"),
]
for fn in samples:
    try:
        fn()
    except Exception as e:
        print(f"{type(e).__name__:<16} {e}")
Python (loads on first run)Ctrl+Enter runs

print is legitimate. The f"{x=}" form prints both the name and the value. For anything longer-lived, the logging gives you levels, timestamps and the ability to switch output off without deleting lines.

import logging

logging.basicConfig(level=logging.DEBUG, format="%(levelname)s %(funcName)s: %(message)s")
log = logging.getLogger(__name__)

def service_fee(amount, rate=0.1):
    log.debug(f"{amount=} {rate=}")
    if amount < 0:
        log.warning("negative amount, treating as zero")
        amount = 0
    result = round(amount * rate, 2)
    log.info(f"fee is {result}")
    return result

service_fee(120)
service_fee(-5)
Python (loads on first run)Ctrl+Enter runs

states something you believe is true at that point in the program. When the belief is wrong, it fails loudly and early instead of letting bad data travel. breakpoint() drops you into the interactive debugger on your own machine (not in this sandbox), where n steps, p prints and c continues.

def split_bill(total, people):
    assert people > 0, f"need at least one person, got {people}"
    assert total >= 0, "total cannot be negative"
    share = total / people
    return round(share, 2)

print(split_bill(100, 3))
try:
    split_bill(100, 0)
except AssertionError as e:
    print("caught:", e)
Python (loads on first run)Ctrl+Enter runs

Common question

Does an error message mean I failed?

A traceback the chain of calls that led to an exception, ending with the exception type and message. Read it from the bottom: the message says what went wrong, the last frame says where, and the frames above show how the program got there. The type alone often names the cause: NameError for a typo, TypeError for a None where an object was expected, KeyError for a missing key.

Debugging is "something is wrong" down to "this line, with this input". Reproduce the failure with the smallest input that triggers it; print or log the values along the way; decide what you expect before you look; and once you find the fix, add a test that would have caught it.

Source: The Python tutorial, on errors and exceptions: "The last line of the error message indicates what happened."

Common mix-ups

Each of these sounds reasonable. Think about why it is wrong, then open the explanation.

  • A good programmer writes code that doesn't produce errors.

  • The first line of a traceback shows where the bug is.

  • print statements are unprofessional; a debugger is the only proper tool.

Practice

This should return the median of a , but it has two bugs: one crashes on even-length lists and one gives wrong answers for unsorted input. Run it, read the traceback, and fix both.

Exercise

Fix the median

3 checks
def median(values):
    n = len(values)
    middle = n / 2
    if n % 2 == 1:
        return values[middle]
    return (values[middle - 1] + values[middle]) / 2


print(median([3, 1, 2]))
print(median([4, 1, 3, 2]))
 
Python (loads on first run)Ctrl+Enter runs
Output
Press Run to try your code, then Check answer to grade it.

Write safe_get(data, path) that walks a nested dict/list structure following a list of keys and indexes, returning None instead of raising when any step is missing. safe_get(cfg, ["db", "hosts", 0]) should give "alpha".

Exercise

Defensive lookups

4 checks
cfg = {"db": {"hosts": ["alpha", "beta"], "port": 5432}}


def safe_get(data, path):
    for step in path:
        data = data[step]
    return data


print(safe_get(cfg, ["db", "hosts", 0]))
print(safe_get(cfg, ["db", "user"]))
print(safe_get(cfg, ["db", "hosts", 5]))
 
Python (loads on first run)Ctrl+Enter runs
Output
Press Run to try your code, then Check answer to grade it.

Check yourself

Quick check

In a traceback, which frame usually points at the line that actually raised the exception?

Finished reading?2 exercises still open. You can come back to them later.

Tip: use the ← and → keys to move between lessons.