How to Debug Your Python Code Properly

0

How many times have you been put into situations where you had to update somebody else's code. If you are part of a development team my guess is more often than you would like. Well it turns out that python has a neat debugging feature (like most other languages) which comes in very handy in such situations. This guide is quick tutorial which will hopefully make your life easier.

Posted in these interests:
h/python67 guides
h/code69 guides
h/debugging1 guide

Let us consider the simple program below for the purposes of this tutorial.

This program takes two command line arguments and performs addition and subtraction operations.

(Lets assume user inputs valid values, hence we are not error handling).

import sys

def add(num1=0, num2=0):
    return int(num1) + int(num2)

def sub(num1=0, num2=0):
    return int(num1) - int(num2)

def main():
    #Assuming our inputs are valid numbers
    print sys.argv
    addition = add(sys.argv[1], sys.argv[2])
    print addition
    subtraction = sub(sys.argv[1], sys.argv[2])
    print subtraction

if __name__ == '__main__':
    main()

Python comes with a useful module called pdb which is basically an interactive source code debugger.

You need the following lines to actually use this module.

import pdb

pdb.set_trace()

Consider our modified program with the break points included:

import pdb
import sys

def add(num1=0, num2=0):
    return int(num1) + int(num2)

def sub(num1=0, num2=0):
    return int(num1) - int(num2)

def main():
    #Assuming our inputs are valid numbers
    print sys.argv
    pdb.set_trace() # <-- Break point added here
    addition = add(sys.argv[1], sys.argv[2])
    print addition
    subtraction = sub(sys.argv[1], sys.argv[2])
    print subtraction

if __name__ == '__main__':
    main()

Once you have your break points set you can just execute the program like you normally would.

python debugger.py 1 2

The program will halt execution at the first break point it encounters.

['debugger.py']
> /Users/someuser/debugger.py(15)main()
-> addition = add(sys.argv[1], sys.argv[2])
(Pdb)

We had our break point set at line 14, and we see that the next line that will execute is line 15. The program has halted before executing line 15.

We have a few options here... Lets look at some in the steps below.

At you debugger prompt press n to go to the next line.

> /Users/someuser/debugger.py(14)main()
-> addition = add(sys.argv[1], sys.argv[2])
(Pdb) n
> /Users/someuser/debugger.py(15)main()
-> print addition

This executes the current line of code and is now ready to execute the next line.

We can step through the whole program line by line using n, but that would not be very useful.

Also you might have noticed that pdb did not actually step into our add function. Lets look at a few more options to make the debugging more interesting.

Lets start debugging our program again. (You can hit c to make pdb jump to the end or until the next break point. Since we don't have any the program will complete execution).

['debugger.py', '1', '2']
> /Users/someuser/debugger.py(14)main()
-> addition = add(sys.argv[1], sys.argv[2])
(Pdb)

Now if we want to know what sys.argv contains we can type:

-> addition = add(sys.argv[1], sys.argv[2])
(Pdb) p sys.argv
['debugger.py', '1', '2']
(Pdb) p sys.argv[1]
'1'
(Pdb)

This is pretty handy to check out what values our variable actually store.

Now lets step into the addition function.

We can step into our addition function using "s".

(Pdb) s
--Call--
> /Users/someuser/debugger.py(4)add()
-> def add(num1=0, num2=0):
(Pdb) n
> /Users/someuser/debugger.py(5)add()
-> return int(num1) + int(num2)
(Pdb)

This put us inside our add function, and now we can use n, p and other operators within the add function.

Hitting r at this point will take us to the return statement of the function we stepped into.

This is useful if you want to quickly jump to the end of a function.

We have used pdb.set_trace() to set a breakpoint before we run the program.

Often we want to add break points at specific points in the program after our debugging session has begun.

The option b is our friend here.

Lets start executing our program again.

['debugger.py', '1', '2']
> /Users/someuser/debugger.py(15)main()
-> addition = add(sys.argv[1], sys.argv[2])
(Pdb)

Now let me set a breakpoint on line 18.

-> addition = add(sys.argv[1], sys.argv[2])
(Pdb) b 18
Breakpoint 1 at /Users/someuser/debugger.py:18
(Pdb) c
We are in add--
3
> /Users/someuser/debugger.py(18)main()
-> print subtraction
(Pdb) p subtraction
-1
(Pdb)

As we see above pdb jumped right to line 18 and is waiting for the next action.

Also pdb assigned a number to to the break point (in this case 1). We can user enable/disable breakpoint number to enable/disable the break point for future executions.

Sometimes while debugging you get lost as to exactly where in code you are. Using l in this case prints a nice summary of where you are in the code.

['debugger.py', '1', '2']
> /Users/someuser/debugger.py(15)main()
-> addition = add(sys.argv[1], sys.argv[2])
(Pdb) l
 10
 11     def main():
 12         #Assuming our inputs are valid numbers
 13         print sys.argv
 14         pdb.set_trace() # <-- Break point added here
 15  ->     addition = add(sys.argv[1], sys.argv[2])
 16         print addition
 17         subtraction = sub(sys.argv[1], sys.argv[2])
 18         print subtraction
 

It is also useful to know that you can assign variables during your debugging session to help with your debugging. Consider:

['debugger.py', '1', '2']
> /Users/someuser/debugger.py(15)main()
-> addition = add(sys.argv[1], sys.argv[2])
(Pdb) n
We are in add--
> /Users/someuser/debugger.py(16)main()
-> print addition
(Pdb) p addition
3 #<--- addition here is 3
(Pdb) addition = 'this is now string' #<--- We changed the value of additon
(Pdb) n
this is now string #<--- Now when we print it we actually gets it as a string. that we just set above.
> /Users/someuser/debugger.py(17)main()
-> subtraction = sub(sys.argv[1], sys.argv[2])

Note: If you want to set some variable like n ( which is a pdb command) you should use:

(Pdb) !n=5
(Pdb) p n
5

Finally if you want to quit at any point you can use q. The program being executed is aborted.

This just scratches the surface of pdb. There is a lot more you can do with it pdb docs.

Those using ipython can find a better debugger in ipdb which offers tab completion, syntax highlighting and some other cool features.

If you have other interesting ways of using Pdb let me know in the comments below.

Happy Debugging!

Zach Zach (248)
3 minutes

Everybody makes mistakes. At last, Google has given us the ability to undo sent emails in Gmail.