Python debugging with pdb

Python Debugging

pdb, short for python debugger is a standard built-in module used to debug python code interactively. You can set breakpoints, see variable values, step inside routines, test run code, etc once you enter the pdb. This tutorial demonstrates the best practices of using the python debugger and the latest breakpoint() feature.

Python Debugging with Pdb. Photo by Jenner V.

1. Introduction

To demonstrate, I created a file called sample.py and placed the following code inside.

# sample.py
def add(a, b):
    return a + b

def samplefunc():
    var = 10
    print("One. Line 1 ..")
    print("Two. Line 2 ..!")
    out = add("summy", var)
    print('Three. Line 3 .. Done!')
    return out

samplefunc()

It defines two functions. The samplefunc() function calls add() from within.

If you look at it, clearly, calling the samplefunc() will throw an error upon hitting the add() because it tries to add a string “summy” with the number 10. You can’t add an int and a string.

But let’s pretend we don’t know where the error is and start debugging instead.

2. How to use pdb?

Step 1: Load pdb module and insert set_trace() where you want to start watching the execution.

Step 2: Run the code.

You can run the code anywhere, be it in command line using python sample.py, a python console like IDLE or pretty much anywhere you can run python.

Let’s add the pdb.set_trace() in the first line of samplefunc().

# sample.py
import pdb

def add(a, b):
    return a + b

def samplefunc():
    pdb.set_trace()  # -- added breakpoint
    var = 10
    print("One. Line 1 ..")
    print("Two. Line 2 ..!")
    out = add("summy", var)
    print('Three. Line 3 .. Done!')
    return out

samplefunc()
$ python3 sample.py
-> var = 10
(Pdb) 

Upon running the code, pdb’s debugger console starts where set_trace() is placed, waiting for your instructions.

The arrow mark points to the line that will be run next by pdb.

So, how to navigate from here?

2.1. Execute the next line with — n (next)

Upon entering n, the line var = 10 was executed.

Now the bottom most arrow mark tells us what line will be executed next.

(Pdb) n
-> print("One. Line 1 ..")
(Pdb) 

2.2. Repeat previous command with — ENTER key

Now to execute the next line you can either hit n again. Or just typing the enter key will repeat the previous command issued.

(Pdb) 
One. Line 1 ..
-> print("Two. Line 2 ..!")

2.3. Print the variable values with — p (print)

(Pdb) p var
10
(Pdb) 

2.4. See source code around current line with — l (list)

Use the l command (l for list) to look around few lines above and few lines below.

Pdb) l
  6     
  7     def samplefunc():
  8         pdb.set_trace()  # -- added breakpoint
  9         var = 10
 10         print("One. Line 1 ..")
 11         print("Two. Line 2 ..!")
 12  ->     out = add("summy", var)
 13         print('Three. Line 3 .. Done!')
 14         return out
 15     
 16     samplefunc()
(Pdb) 

2.5. Step into a subroutine with — s (step)

The next line waiting to run is a function add("summy", var).

If you suspect something could be wrong here, you can step into this function using s command and start running this function line-by-line.

-> out = add("summy", var)
(Pdb) s
--Call--
-> def add(a, b):
(Pdb) 

2.6. Continue till current function returns with — r (return)

Fortunately our add() routine is short.

But if you entered a rather looong call, you can continue until the current routine returns by issuing the r command.

(Pdb) r
--Return--
-> return a + b
(Pdb)

2.7. Stop debugger and continue running normally using — c (continue)

If you are done with the debugging and want the program to continue normally, use the c command.

(Pdb) c
$ 

2.8. Quit debugging abruptly with — q (quit)

Else if you want to quit the debugger abruptly, issue the q command.

(Pdb) q
Traceback (most recent call last):
  File "sample.py", line 16, in <module>
    samplefunc()
  File "sample.py", line 12, in samplefunc
    out = add("summy", var)
  File "/Users/selva/anaconda3/lib/python3.6/bdb.py", line 110, in dispatch_exception
    if self.quitting: raise BdbQuit
bdb.BdbQuit
$

Let’s summarise the debugger commands.

Top pdb Commands

CommandKeyDescription
NextnExecute the next line
PrintpPrint the value of the variable following p
RepeatEnterRepeat the last entered command
ListlShow few lines above and below the current line
StepsStep into a subroutine
ReturnrRun until the current subroutine returns
ContinuecStop debugging the current breakpoint and continue normally
QuitqQuit pdb abruptly

3. How to invoke pdb without even modifying the script?

You can invoke pdb debugging without inserting a pdb.set_trace() by calling the script from the command line with the pdb option.

This will execute the script directly in the debugging mode.

$python3 -m pdb sample.py

$ python -m pdb sample.py
-> def add(a, b):
(Pdb) 

4. How to start an interactive shell once the program terminates with an error?

If you run the script with the -i option (see below), an interactive python shell starts upon encountering an error.

$python3 -i sample.py

Then, you can look around and see the values of variables, in the same state when the error occurred.

$ python3 -i sample.py
One. Line one ..
Two. Line two ..!
Traceback (most recent call last):
  File "sample.py", line 16, in <module>
    samplefunc()
  File "sample.py", line 12, in samplefunc
    out = add("summy", var)
  File "sample.py", line 5, in add
    a + b
TypeError: must be str, not int
>>> 

If that did not give a clue of what caused the problem, you can initiate a post mortem at the last traceback.

This starts the debugger precisely at the line that triggered the error.

>>> import pdb
>>> pdb.pm()
-> a + b
(Pdb) 

5. Save execution trace in a log file

Sometimes you don’t want to actually debug, but just want the save the trace to a log file.

$python -m trace -t sample.py > execution.log

6. The new breakpoint() feature

A new feature to insert a breakpoint without explicitly import pdb was proposed. As of python 3.7 and onwards, this will become a reality.

The primary reason to have this feature built-in is to save keystrokes.

All breakpoint() does is to import pdb and call pdb.set_trace(). The rest of the debugging process remains the same.

def samplefunc():
    breakpoint()  # -- add breakpoint
    var = 10
    print("One. Line 1 ..")
    print("Two. Line 2 ..!")
    out = add("summy", var)
    print('Three. Line 3 .. Done!')
    return out

Also, you can temporarily turn off debugging without removing the breakpoints in your script by setting the environment variable PYTHONBREAKPOINT=0.

PYTHONBREAKPOINT=0 python3.7 debugger.py

7. Conclusion

We’ve covered some of the standard ways to debug python code using the python debugger. If you happen to use a better strategy you are welcome to share in the comments area.

Leave a Reply

Your email address will not be published. Required fields are marked *