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?’.