pdb, the Python Debugger

Sometimes it’s nice to get down and dirty with your code and just figure out exactly what it’s trying to do. Luckily python provides us with pdb the python debugger. This tutorial will get you started with it.

tl;dr

Add import pdb; pdb.set_trace() to your code at points where you want to debug. In the debugger you can step into code and you can execute arbitrary statements by prefixing them with !.

For details, continue reading.

Naive debugging

Let’s assume for a moment that you have a new virtualenv. If you’ve completed the virtualenv tutorial you can create a temporary one like so:

mktmpenv

Let’s make a python script called foo.py:

def sum_of_squares(a, b):
    c_squared = a^2 + b^2
    return c_squared


if __name__ == "__main__":
    print sum_of_squares(3, 4)

Note: The mistakes above are for demonstration purposes.

Our expectation is that we’d get the number 25, but instead we get 7 when we run this program.

A naive debugging strategy is to litter the code with print statements:

def sum_of_squares(a, b):
    a_squared = a^2
    b_squared = b^2
    print a^2
    print b^2

    c_squared = a^2 + b^2
    print c_squared
    return c_squared


if __name__ == "__main__":
    print sum_of_squares(3, 4)

This certainly works, but it has it’s limitations. For one, if your script produces a lot of output, you might miss those printed items. Secondly you might forget what variable is what so you may add more print statements. Eventually you’ll be overwhelmed with print statements, figure out your bug, and then carefully undo all the prints that you used for debug.

For our contrived example that might not be a problem, but hopefully you are writing complex and challenging code that could benefit from more.

Enter pdb

pdb lets us debug items interactively and step into functions to follow what our code is doing. We can take our original example and augment it by doing the following:

def sum_of_squares(a, b):
    c_squared = a^2 + b^2
    return c_squared


if __name__ == "__main__":
    import pdb; pdb.set_trace()
    print sum_of_squares(3, 4)

Now I get the following:

» python foo.py
> /private/tmp/foo.py(10)<module>()
-> print sum_of_squares(3, 4)
(Pdb)

You can use ? to get help, but the arrow indicates the line that’s about to be executed. At this point it’s helpful to hit s to step into that line.

(Pdb) s
--Call--
> /private/tmp/bar.py(2)sum_of_squares()
-> def sum_of_squares(a, b):

This is a call to a function. If you want an overview of where you are in your code, try l:

(Pdb) l
  1
  2  -> def sum_of_squares(a, b):
  3
  4             c_squared = a^2 + b^2
  5             return c_squared
  6
  7
  8     if __name__ == "__main__":
  9         import pdb; pdb.set_trace()
 10         print sum_of_squares(3, 4)
[EOF]
(Pdb)

You can hit n to advance to the next line. At this point you are inside the sum_of_squares method and you have access to the variables in it’s scope, namely a and b. You can view those by doing !a or !b. Here’s some a snippet of debugging:

(Pdb) n
> /private/tmp/bar.py(4)sum_of_squares()
-> c_squared = a^2 + b^2
(Pdb) !a
3
(Pdb) !b
4
(Pdb) !a^2
1
(Pdb) !b^2
6

We can see that the ^ operator is not what it seems. We might even recall that in many computer languages what we want is the ** operator to do squares:

(Pdb) !a**2, b**2
(9, 16)

Voila! We were able to step in and around the execution paths of our code and successfully debug our software.

Final thoughts

Get familiar with the python debugger. It’ll let you be able to interact with your code immediately instead of waiting for you to re-run your code and add new print statements.