Python Technical Guide: An Introduction to PDB

Any software developer knows that good code doesn’t get written right the first time, and when unexpected behavior occurs eyeballing the code or even using print statements can end up falling short. This year at PyCon, I had the pleasure of attending a talk by Clayton Parker about an extensively useful Python module affectionately called pdb.

The official Python documents define pdb as “an interactive source code debugger for Python programs.” Those programmers that use IDE’s like Eclipse or PyCharm will probably be familiar with the concept of an interactive debugger as they come with one baked in. This article will assume that you are not using one of these debuggers. Now, let’s look at an example of its use.

import pdb message = "The circumference of the circle is " pdb.set_trace() pi = 3.1415 r = 5 def calculate circumference(pi, radius): return 2pir print message + (calculate circumference(pi, r))

Let’s take a look at what we’ve got here. It’s a simple program that prints out the circumference of a circle with a radius of 5. At the top we’ve imported the pdb module and on line 4 we’ve added the line pdb.set_trace() , which is what’s called a breakpoint for the code to stop at on execution. Now, let’s go ahead and run this program with python circle.py . Here’s what we get:

/Users/kurt/ExcellaDevelop/python/circle.py(5)() -> pi = 3.1415 (Pdb)

The debugger has stopped the execution of the program and is showing the next line after the breakpoint. It is important to note that the line that pdb shows has not been executed yet. So, what if instead you’re debugging a massive file and you forget where you are in the code? There’s a command for that! Enter the command l  or list . That will list out 11 lines of code and an arrow on the line that the debugger is stopped on.

(Pdb) l 1 import pdb 2 3 message = "The circumference of the circle is " 4 pdb.settrace() 5 -> pi = 3.1415 6 r = 5 7 8 9 def calculatecircumference(pi, radius): 10 return 2pir 11

Now that we know where we are in the program, we can try printing out some values using the command p  or print . You can enter the command, p pi  or p pi, r  to get the value(s) of the variables. If we try to print out a variable that has not yet been instantiated then we’ll get a NameError, as seen below. Now, let’s traverse through the program with the n  or next  command.

(Pdb) n > /Users/kurt/Excella/Develop/python/circle.py(6)() -> r = 5 (Pdb) p pi, r * NameError: NameError("name 'r' is not defined",) (Pdb) p pi 3.1415

Great, just as expected. It executed line 5 and is now showing line 6, but it hasn’t executed line 6 yet. That’s demonstrated by trying to print out r  before it’s declared. If we decided that we only wanted to execute the program up to this point, we could just use the q  or quit  command to exit the program. However, this is a bit dirty and you could also use c  or continue  to execute the rest of the program to completion (or the next breakpoint).

But we’re not done yet. Let’s move on to the next line. What if we want to test out to code as if the variable r  had a different value? The debugger will let you do that! Let’s set r = 8 .

(Pdb) n > /Users/kurt/Excella/Develop/python/circle.py(6)() -> r = 5 (Pdb) n > /Users/kurt/Excella/Develop/python/circle.py(9)() -> def calculatecircumference(pi, radius): (Pdb) r = 8 > /Users/kurt/Excellanevelop/python/circle.py(12)() -> print message + str(calculatecircumference(pi, r))

What happened? It accepted the input r  and executed a built-in command called return, which resumes execution until the function returns. The way around this is to put a !  before, so now the command is !r = 8 .

/Users/kurt/Excella/Develop/python/circle.py(9)() -> def calculate_circumference(pi, radius): (Pdb) !r = 8 (Pdb)

That’s more like it. The last command that we’ll discuss is the s  or step  command. If we run next  again we’ll skip the function definition because it’s not part of the current execution and get to the last line. This calls the function calculate_circumference()  and returns the circumference. When this line is next in execution step  will “step into” the function call.

(Pdb) s --Call-- > /Users/kurt/Excella/Develop/python/circle.py(9)calculatecircumference() -> def calculatecircumference(pi, radius): (Pdb) n > /Users/kurt/Excella/Develop/python/circle.py(10)calculatecircumference() -> return 2*pi*r (Pdb) --Return-- > /Users/kurt/Excella/Develop/python/circle.py(10)calculatecircumference()->50.264 -> return 2pir (Pdb) The circumference of the circle is 50.264 --Return-- > /Users/kurt/Excella/Develop/python/circle.py(12)()->None -> print message + str(calculate_circumference(pi, r))

That’s all we’ll cover now, but that’s sure not the whole of it. There is still plenty to learn and experiment with pdb. I urge you to use pdb for your debugging needs in python if you’re not already doing so, and keep doing it if you are!

There’s a python package called ipdb that adds syntax highlighting and completion. I highly suggest that you check it out if you have a chance.

kurtw

0