Debugging Python programs

Table of Contents

Four approaches to debugging.

Thorough test-driven programming

"A bug is a test case you haven't written yet."

– Mark Pilgrim

Judiciously placed print statements

The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.

– Kernighan & Plauger "Unix for Beginners" (1979)

Earlier this week, a student asked me about this code

def bla(lot):
    first = lot[0]
    smallest = first[-1]
    for n in lot:
        if n[-1] < smallest:
            u = 1
    return u

print(bla([(1,2),(3,),(),(4,5,6)]))

When run, this happens:

$ python tuples.py
Traceback (most recent call last):
  File "tuples.py", line 9, in <module>
    print(bla([(1,2),(3,),(),(4,5,6)]))
  File "tuples.py", line 5, in bla
    if n[-1] < smallest:
IndexError: tuple index out of range
$

But if we insert some print statements

def bla(lot):
    print('lot is', lot)
    first = lot[0]
    print('first is', first)
    smallest = first[-1]
    print('smallest is', smallest)
    for n in lot:
        print('n is', n)
        if n[-1] < smallest:
            u = 1
    return u

print(bla([(1,2),(3,),(),(4,5,6)]))

we get some clues what the problem might be

$ python tuples.py
lot is [(1, 2), (3,), (), (4, 5, 6)]
first is (1, 2)
smallest is 2
n is (1, 2)
n is (3,)
n is ()
Traceback (most recent call last):
  File "tuples.py", line 13, in <module>
    print(bla([(1,2),(3,),(),(4,5,6)]))
  File "tuples.py", line 9, in bla
    if n[-1] < smallest:
IndexError: tuple index out of range
$

Add assertions

Since it can be so easy to rapidly write working Python code, it's also easy to neglect to write clear and well thought out comments as we go (remember types, signatures, purpose, etc.). Assertions can be thought of as executable comments that only kick in when the code is used in a way that doesn't satisfy the assumptions made when writing it. They have a number of advantages.

Assertions should be used to test conditions that should never happen. (In contrast Exceptions should be used for errors that can conceivably happen.)

You can use assertions to:

  1. check parameter types, classes, or values
  2. check data structure invariants
  3. check "can't happen" situations (duplicates in a list, contradictory state variables.)
  4. make sure a function's return value is reasonable.

This example is overkill but it illustrates how you can state what your expectations are about the types and values of inputs or variables.

from math import sqrt


def pythagoras(x, y):
    """Works only for integers."""
    assert isinstance(x, int), "x needs to be an int"
    assert isinstance(y, int), "y needs to be an int"
    assert x > 0, "x needs to be positive"
    assert y > 0, "y needs to be positive"
    return sqrt(x**2 + y**2)

A debugger

In the code

Using pdb

import pdb

pdb.set_trace()

For example

import pdb


def bla(lot):
    pdb.set_trace()
    first = lot[0]
    smallest = first[-1]
    for n in lot:
        if n[-1] < smallest:
            u = 1
    return u


print(bla([(1, 2), (3,), (), (4, 5, 6)]))

From the command line

$ python -m pdb ~/tmp/tr.py

Author: Breanndán Ó Nualláin <o@uva.nl>

Date: 2025-09-04 Thu 08:55