The Python Debugger – PDB

Bugs are an inevitable part of a programmer’s life.

A bug is an error in your code that makes your program produce unexpected results.

Debugging is the process of locating the source of the error and fix it.

The overall debugging process is:

  • Set breakpoints
  • Go through your code line-by-line checking variables and values
  • Fix any errors that might appear
  • Re-run the code and check if everything is ok, if not, go back to step 1.

The Python Debugger, or simply PDB, gives you the ability to execute and inspect your code line-by-line, so you see the exact path your code takes until it finishes.

Python allows you to execute a program from the start in debug mode by calling the pdb module with -m when executing the code.

Save the file below in print_even_values.py and execute it with python3 -m pdb print_even_values.py.

def print_items_even_values(dictionary):
    for item in dictionary:
        if(dictionary[item] % 2 == 0):
            print(f'The key {item} points to {dictionary[item]}')

if __name__ == '__main__':
    shopping_dict = {'soap': 1, 'meat': 2, 'rice': 3, 'apples': 4}
    print_items_even_values(shopping_dict)

You should see an output similar to this one:

> /Users/renanmoura/print_even_values.py(1)<module>()
-> def print_items_even_values(dictionary):
(Pdb)

The PDB command line interface is activated and you can use the many commands at your disposal.

To see them, type help:

(Pdb) help

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt      
alias  clear      disable  ignore    longlist  r        source   until    
args   commands   display  interact  n         restart  step     up       
b      condition  down     j         next      return   tbreak   w        
break  cont       enable   jump      p         retval   u        whatis   
bt     continue   exit     l         pp        run      unalias  where    

Miscellaneous help topics:
==========================
exec  pdb

As you can see, there are a lot of commands available, but you won’t have to worry about most of them most of the time.

Type list or ll and Pdb will show the whole code with a -> indicating the position of where the debugger is.

(Pdb) ll
  1  -> def print_items_even_values(dictionary):
  2         for item in dictionary:
  3             if(dictionary[item] % 2 == 0):
  4                 print(f'The key {item} points to {dictionary[item]}')
  5     
  6     if __name__ == '__main__':
  7         shopping_dict = {'soap': 1, 'meat': 2, 'rice': 3, 'apples': 4}
  8         print_items_even_values(shopping_dict)

If you type n which means ‘next’, the code will be executed line-by-line and the current position will be displayed with ->.

Notice how it jumps to if name == 'main' to start the execution, then shopping_dict is initialized, and then our function print_items_even_values(shopping_dict) is called.

(Pdb) n
> /Users/renanmoura/print_even_values.py(6)<module>()
-> if __name__ == '__main__':
(Pdb) n
> /Users/renanmoura/print_even_values.py(7)<module>()
-> shopping_dict = {'soap': 1, 'meat': 2, 'rice': 3, 'apples': 4}
(Pdb) n
> /Users/renanmoura/print_even_values.py(8)<module>()
-> print_items_even_values(shopping_dict)
(Pdb) n
The key meat points to 2
The key apples points to 4

Here is an important detail, if you just hit n on a function call, the function will be called and the program will continue to the next command after the function call.

If you want to get into the function to debug the code inside it, you have to call step or simply s.

Notice that we call s after calling n three times to get into the function, then we call n normally to go through the function itself.

> /Users/renanmoura/print_even_values.py(1)<module>()
-> def print_items_even_values(dictionary):
(Pdb) n
> /Users/renanmoura/print_even_values.py(6)<module>()
-> if __name__ == '__main__':
(Pdb) n
> /Users/renanmoura/print_even_values.py(7)<module>()
-> shopping_dict = {'soap': 1, 'meat': 2, 'rice': 3, 'apples': 4}
(Pdb) n
> /Users/renanmoura/print_even_values.py(8)<module>()
-> print_items_even_values(shopping_dict)
(Pdb) s
--Call--
> /Users/renanmoura/print_even_values.py(1)print_items_even_values()
-> def print_items_even_values(dictionary):
(Pdb) n
> /Users/renanmoura/print_even_values.py(2)print_items_even_values()
-> for item in dictionary:
(Pdb) n
> /Users/renanmoura/print_even_values.py(3)print_items_even_values()
-> if(dictionary[item] % 2 == 0):
(Pdb) n
> /Users/renanmoura/print_even_values.py(2)print_items_even_values()
-> for item in dictionary:
(Pdb) n
> /Users/renanmoura/print_even_values.py(3)print_items_even_values()
-> if(dictionary[item] % 2 == 0):
(Pdb) n
> /Users/renanmoura/print_even_values.py(4)print_items_even_values()
-> print(f'The key {item} points to {dictionary[item]}')
(Pdb) n
The key meat points to 2
> /Users/renanmoura/print_even_values.py(2)print_items_even_values()
-> for item in dictionary:
(Pdb) item
'meat'
(Pdb) ll
  1     def print_items_even_values(dictionary):
  2  ->     for item in dictionary:
  3             if(dictionary[item] % 2 == 0):
  4                 print(f'The key {item} points to {dictionary[item]}')
(Pdb) dictionary[item] % 2 == 0
True

Also notice how called item to check the current value of the for loop, which is ‘meat’ in this case.

This means you can call any variable name available in the scope to check its value, avoiding the need to call multiple print() statements as shown in the ‘Naive Way’ section.

Then we call ll to show where we are right now with ->.

You can even execute code by manually ahead of time, for instance, we can execute the condition in the if statement to check if it will return True or False for ‘meat’.

To get out of debug mode, just type q or quit.

You can also set a so-called breakpoint with pdb.set_trace(), and the debugger will stop the execution on the line right below the breakpoint.

You have to import the corresponding module with import pdb.

Notice the pdb.set_trace() after the for loop, meaning the breakpoint is set on the line below it, the if statement.

Execute the program normally with python3 print_even_values.py.

import pdb

def print_items_even_values(dictionary):
    for item in dictionary:
        pdb.set_trace()
        if(dictionary[item] % 2 == 0):
            print(f'The key {item} points to {dictionary[item]}')

if __name__ == '__main__':
    shopping_dict = {'soap': 1, 'meat': 2, 'rice': 3, 'apples': 4}
    print_items_even_values(shopping_dict)

You will notice how the debug mode will start on the if statement, where we set our breakpoint, from there, you can use n to continue the execution as we did before.

The first item ‘soap’ with value ‘1’, does not pass the if condition, so the code immediately goes to the next iteration to try the next item ‘meat’ with value ‘2’, and so on.

> /Users/renanmoura/print_even_values.py(6)print_items_even_values()
-> if(dictionary[item] % 2 == 0):
(Pdb) n
> /Users/renanmoura/print_even_values.py(4)print_items_even_values()
-> for item in dictionary:
(Pdb) n
> /Users/renanmoura/print_even_values.py(5)print_items_even_values()
-> pdb.set_trace()
(Pdb) n
> /Users/renanmoura/print_even_values.py(6)print_items_even_values()
-> if(dictionary[item] % 2 == 0):
(Pdb) n
> /Users/renanmoura/print_even_values.py(7)print_items_even_values()
-> print(f'The key {item} points to {dictionary[item]}')
(Pdb) item
'meat'
(Pdb) n
The key meat points to 2
> /Users/renanmoura/print_even_values.py(4)print_items_even_values()
-> for item in dictionary:

Finally, we can also set break points while running the code, go back to print_even_values.py, remove import pdb and pdb.set_trace() to look the way it was originally:

def print_items_even_values(dictionary):
    for item in dictionary:
        if(dictionary[item] % 2 == 0):
            print(f'The key {item} points to {dictionary[item]}')

if __name__ == '__main__':
    shopping_dict = {'soap': 1, 'meat': 2, 'rice': 3, 'apples': 4}
    print_items_even_values(shopping_dict)

Now execute the module with python3 -m pdb print_even_values.py.

When in debug mode with Pdb, call l and you will see you are at the beginning of the file at the declaration of the function.

We have no breakpoints, so we can call b or break to set a breakpoint on a given line, here we are setting the breakpoint on line 3 at the if statement with b 3.

If you call b by itself, it will list the breakpoints, in our case, there is only one breakpoint on line 3.

Then we can call c or continue to continue the execution of the code until it finds a breakpoint, you will notice it will stop at the if statement where we set the breakpoint on line 3, then you can use the other commands that we already demonstrated like n to execute line-by-line or call a variable name to check its current value.

> /Users/renanmoura/print_even_values.py(1)<module>()
-> def print_items_even_values(dictionary):
(Pdb) l
  1  -> def print_items_even_values(dictionary):
  2         for item in dictionary:
  3             if(dictionary[item] % 2 == 0):
  4                 print(f'The key {item} points to {dictionary[item]}')
  5     
  6     if __name__ == '__main__':
  7         shopping_dict = {'soap': 1, 'meat': 2, 'rice': 3, 'apples': 4}
  8         print_items_even_values(shopping_dict)
[EOF]
(Pdb) b 3
Breakpoint 1 at /Users/renanmoura/print_even_values.py:3
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/renanmoura/print_even_values.py:3
(Pdb) c
> /Users/renanmoura/print_even_values.py(3)print_items_even_values()
-> if(dictionary[item] % 2 == 0):
(Pdb) break 4
Breakpoint 2 at /Users/renanmoura/print_even_values.py:4
(Pdb) disable 2
Disabled breakpoint 2 at /Users/renanmoura/print_even_values.py:4
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/renanmoura/print_even_values.py:3
    breakpoint already hit 2 times
2   breakpoint   keep no    at /Users/renanmoura/print_even_values.py:4
    breakpoint already hit 1 time
(Pdb) enable 2
Enabled breakpoint 2 at /Users/renanmoura/print_even_values.py:4
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /Users/renanmoura/print_even_values.py:3
    breakpoint already hit 2 times
2   breakpoint   keep yes   at /Users/renanmoura/print_even_values.py:4
    breakpoint already hit 1 time
(Pdb) clear
Clear all breaks? y
Deleted breakpoint 1 at /Users/renanmoura/print_even_values.py:3
Deleted breakpoint 2 at /Users/renanmoura/print_even_values.py:4
(Pdb) b

You can also enable or disable breakpoints by referencing their ‘Num’ ID showed when calling b, here we disabled and enabled 2 by calling disable 2 and enable 2.

The disabled breakpoint won’t stop the execution of the program until you enable it again.

You can see which breakpoints are enabled/disabled by looking at the ‘Enb’ column when calling b.

To clear all the breakpoints call clear and answer ‘yes’ when asked ‘Clear all breaks?’.