Python 101: An Introduction to Python’s Debugger

Python comes with its own debugger module that is named pdb. This module provides an interactive source code debugger for your Python programs. You can set breakpoints, step through your code, inspect stack frames and more. We will look at the following aspects of the module:

  • How to start the debugger
  • Stepping through your code
  • Setting breakpoints

Let’s start by creating a quick piece of code to attempt debugging with. Here’s a silly example:

# debug_test.py
 
#----------------------------------------------------------------------
def doubler(a):
    """"""
    result = a*2
    print(result)
    return result
 
#----------------------------------------------------------------------
def main():
    """"""
    for i in range(1,10):
        doubler(i)
 
if __name__ == "__main__":
    main()

Now let’s learn how to run the debugger against this piece of code.


How to Start the Debugger

You can start the debugger three different ways. The first is to just import it and insert **pdb.set_trace()** into your code to start the debugger. You can import the debugger in IDLE and have it run your module. Or you can call the debugger on the command line. We’ll focus on the last two methods in this section. We will start with using it in the interpreter (IDLE). Open up a terminal (command line window) and navigate to where you save the above code example. Then start Python. Now do the following:

>>> import debug_test
>>> import pdb
>>> pdb.run('debug_test.main()')
> <string>(1)<module>()
(Pdb) continue
2
4
6
8
10
12
14
16
18
>>>

Here we import our module and pdb. Then we execute pdb’s run method and tell it to call our module’s main method. This brings up the debugger’s prompt. Here we typed continue to tell it to go ahead and run the script. You can also type the letter c as a shortcut for continue. When you continue the debugger will continue execution until it reaches a breakpoint or the script ends.

The other way to start the debugger is to execute the following command via your terminal session:

python -m pdb debug_test.py

If you run it this way, you will see a slightly different result:

-> def doubler(a):
(Pdb) c
2
4
6
8
10
12
14
16
18
The program finished and will be restarted

You will note that in this example we used c instead of continue. You will also note that the debugger restarts at the end. This preserves the debugger’s state (such as breakpoints) and can be more useful than having the debugger stop. Sometimes you’ll need to go through the code several times to understand what’s wrong with it.

Let’s dig a little deeper and learn how to step through the code.


Stepping Through the Code

If you want to step through your code one line at a time, then you can use the step (or simply “s”) command. Here’s a session for your viewing pleasure:

C:\Users\mike>cd c:\py101
 
c:\py101>python -m pdb debug_test.py
> c:\py101\debug_test.py(4)<module>()
-> def doubler(a):
(Pdb) step
> c:\py101\debug_test.py(11)<module>()
-> def main():
(Pdb) s
> c:\py101\debug_test.py(16)<module>()
-> if __name__ == "__main__":
(Pdb) s
> c:\py101\debug_test.py(17)<module>()
-> main()
(Pdb) s
--Call--
> c:\py101\debug_test.py(11)main()
-> def main():
(Pdb) next
> c:\py101\debug_test.py(13)main()
-> for i in range(1,10):
(Pdb) s
> c:\py101\debug_test.py(14)main()
-> doubler(i)
(Pdb)

Here we start up the debugger and tell it to step into the code. It starts at the top and goes through the first two function definitions. Then it reaches the conditional and finds that it’s supposed to execute the main function. We step into the main function and then use the next command. The next command will execute a called function if it encounters it without stepping into it. If you want to step into the called function, then you’ll only want to just use the step command.

When you see a line like > c:\py101\debug_test.py(13)main(), you will want to pay attention to the number that’s in the parentheses. This number is the current line number in the code.

You can use the args (or a) to print the current argument list to the screen. Another handy command is jump (or j) followed by a space and the line number that you want to “jump” to. This gives you the ability to skip a bunch of monotonous stepping to get to the line that you want to get to. This leads us to learning about breakpoints!


Setting breakpoints

A breakpoint is a line in the code where you want to pause execution. You can set a breakpoint by calling the **break** (or **b**) command followed by a space and the line number that you want to break on. You can also prefix the line number with a filename and colon to specify a breakpoint in a different file. The break command also allows you to set a breakpoint with the **function** argument. There is also a **tbreak** command which will set a temporary breakpoint which is automatically removed when it gets hit.

Here’s an example:

c:\py101>python -m pdb debug_test.py
> c:\py101\debug_test.py(4)<module>()
-> def doubler(a):
(Pdb) break 6
Breakpoint 1 at c:\py101\debug_test.py:6
(Pdb) c
> c:\py101\debug_test.py(6)doubler()
-> result = a*2

We start the debugger and then tell it to set a breakpoint on line 6. Then we continue and it stops on line 6 just like it’s supposed to. Now would be a good time to check the argument list to see if it’s what you expect. Give it a try by typing args now. Then do another continue and another args to see how it changed.


Wrapping Up

There are a lot of other commands that you can use in the debugger. I recommend reading the documentation to learn about the others. However, at this point you should be able to use the debugger effectively to debug your own code.


Additional Reading

  • memilanuk

    I think where I’ve always gotten ‘lost’ with debugging is that at some point when stepping thru a program I end up getting shunted off from my code (which is where the problem likely lies) into system files like in this example:

    ipdb> s
    > e:pythondatadebug_test.py(7)doubler()
    6 result = a * 2
    —-> 7 print(result)
    8 return result

    ipdb> s
    –Call–
    > e:pythonwinpython-32bit-3.3.3.3python-3.3.3libsite-packagesipythonkernelzmqiostream.py(178)write()
    177
    –> 178 def write(self, string):
    179 if self.pub_socket is None:

    ipdb>

    Is there a recommended way of avoiding this sort of ‘trap’?

  • I would try using the “next” command as it calls functions and stops at the next line in your code. At least, that’s been my impression from the docs and from talking to my co-workers

  • memilanuk

    Hmmm… ‘next’ just keeps going through the system function (‘iostream.py’ in this case). I tried ‘r(eturn)’ which moved thru ‘iostream.py’ faster, but it seems ‘u(ntil)’ is closer to the mark, according to the PMOTW page – unless someone has a better option.

  • Hopefully someone will know as I haven’t been able to replicate the behavior you’re seeing with my example. I’m not going into iostream.py at all no matter what command I use.

  • memilanuk

    Okay… so I’m running this from WinPython 3.3.3 on a usb stick at the moment. If I run it from a WinPython Interpreter window, this is what I get:

    http://pastie.org/8954607

    If I run the debugger from Spyder/IPython, this is what I see:

    http://pastie.org/8954625

    Both of which step *out* of my original script, into other files – though not the same one?!? You can also see the use of ‘u(ntil)’ to jump back into the original file.

  • That’s really weird. I’m just running the pdb module myself. Here’s a paste of my session: http://pastie.org/8954658

  • memilanuk

    Hmmm… I even tried running it from the command prompt as you did:

    http://pastie.org/8954682

    Guess I’ll have to try it from my laptop (Linux) with a ‘regular’ (non-portable) install of Python 3.3 tonight.

  • Pingback: OTR Links 03/21/2014 | doug --- off the record()

  • Eric

    Is there a ‘step out’ command?

  • Not that I can see…

  • I’m a bit confused as well. Maybe ask on the Python mailing list?

  • Eric

    Finally found the “step out” command–

    r(eturn):

    Continue execution until the current function returns.