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:
- check parameter types, classes, or values
- check data structure invariants
- check "can't happen" situations (duplicates in a list, contradictory state variables.)
- 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