Debugging Python Code
When I started learning Python, my super-scientific investigation method for debugging scripts was to insert print(my_variable)
into my code (and occasionally printing my_variable.__dict__
where I expected that method to be implemented), run tests locally, and check out what got printed.
Soon, a more experienced colleague of mine introduced me to the pdb
debugger in Python, and it was definitely a game changer. The fact that you can inspect your code interactively, line by line, can be extremely useful, and I regularly use it while writing tests for my code.
Note: PyCharm and other IDEs can provide advanced, user-friendly interfaces for debugging. However, I don’t think that what they can offer is substantially different from the built-in debugging options. I use
pdb++
which is an extension of the built-inpdb
package - it adds syntax highlighting, auto-complete, and some additional commands, but otherwise it’s the same aspdb
. You can install it with:
$ pip install pdbpp
At the problematic code part where you suspect a bug, you can just type import pdb; pdb.set_trace()
(or the built-in breakpoint()
if you’re above 3.7
), save the file and run some tests. Execution will stop at that line (called the breakpoint), and you will be dropped into an interactive pdb
session.
The following commands can come in handy during that session.
l
- list => lists the source code surrounding your breakpoint.-
sticky
is apdbpp
-specific command, and it’s extremely useful. It makes your context “sticky”, meaning that as you navigate through the code, the source code will “stick” around your breakpoint - it’s like ifll
was executed every time you take a step in the code. c
- continue => continue until the nextbreakpoint()
call. Pretty straightforward!s
- step => execute the current line, and if it’s a function call, step into the function definition. This can come very handy if you aren’t sure where your bug is occurring.n
- next => execute the current line, without stepping into the called function.- until => continue execution until a line number great that the current line is reached. This is useful if you encounter a loop (
for
orwhile
) when debugging some code, eg. if the loop has 100 iterations, you would have to pressn
quite a few times to reach the loop’s end. You can useu
as a shortcut to let the loop finish, and it will stop ant the first line after the loop.
and so on - however, with u
, you can just skip the loop:
-
until can receive an argument, in which case execution will continue until reaching that line.
-
display is a pretty neat command if you are wanting to track down where a variable is mutated. You can give it an expression, and it displays the value of the expression if it changed, each time execution stops. In this example, we are tracking the value of the variable called
number
. The debugger shows what it changed to, and what was the previous value - pretty neat!
Notice the number: <undefined> --> 0
and number: 0 --> 76
parts in the pdb
console, which tell you the sequences of events:
- pp
[expression]
=> pretty-prints the value of a variable. Useful in combination with__dict__
to make the output more human-readable (pp my_object.__dict__
) - source
[expression]
=> tries to get the source code of[expression]
- neat if you don’t want to leave your terminal and quickly want to retrieve a function/class definition.
There are a lot of other useful commands provided by pdb
- but these are the ones that I’m using most often. Checkout out the additional sources for more details.
Happy debugging!