Debugging is an essential skill for any software developer. It allows us to find and fix errors in our code, improve quality and performance, and learn from our mistakes.
One of the most common and powerful tools for debugging Python code is the Python Debugger (PDB). PDB is a module that provides an interactive command-line interface for inspecting and manipulating the state of a running program.
With PDB, we can set breakpoints, step through the code line by line, examine variables and expressions, modify values, and more.
However, using PDB can also be cumbersome and tedious. To invoke PDB, we need to import the pdb module and insert a pdb.set_trace()
statement in our code where we want to pause the execution and enter the debugger.
import pdb
def add(a: int, b:int) -> int:
pdb.set_trace() # pause here and enter the debugger
return a + b
print(add(1, 2))
This means we have to modify our source code every time we want to debug it, and remember to remove or comment out the pdb statements when we are done.
Moreover, if we want to use a different debugger than PDB, such as IPython or PuDB, we have to change the import statement and the function call accordingly.
Fortunately, Python 3.7 introduced a new built-in function that simplifies this process: breakpoint()
. The breakpoint()
function is a convenient way to start a debugger at any point in our code without having to import anything or write debugger-specific code.
All we have to do is insert a breakpoint()
statement where we want to pause the execution, and enter the debugger!
In this article, we will learn what breakpoint()
is, how to use it with PDB and other debuggers, and how to change its behavior with environment variables.
When using PDB, we need to learn a couple of commands to interact with the debugger. There is a long list of commands PDB gives us, but we only need to know a few of them to get started. For example, if we want to print values, we use the p
command.
If we want to execute the next line of code, we use the n
or next
command. To continue execution until the next breakpoint, we use the c
or continue
command. Finally, the l
or list
command shows us the code around the current line we are debugging.
Command |
Description |
---|---|
|
Print the value of an expression. |
|
Continue execution until the next line in the current function is reached or it returns. |
|
Execute the current line, and stop at the first possible occasion (either in a function that is called or in the current function). |
|
Continue execution, only stop when a breakpoint is encountered. |
|
List the source code for the current file. |
|
Prints a list of commands we can use with PDB. |
Let’s see how breakpoint()
works. Below is a recursive function that calculates the factorial of a given number. I’ve added a breakpoint()
statement to the function, so we can pause the execution and enter the debugger.
# main.py
def factorial(number: int) -> int:
if number < 0:
raise ValueError("`n` must be non-negative.")
elif number <= 1:
return 1
else:
breakpoint() # pause here and enter the debugger
return number * factorial(number - 1)
# factorial(5) is: 5 * 4 * 3 * 2 * 1 = 120
print(factorial(5))
When we run this code, the program will stop at the breakpoint()
statement and launch the default debugger, which is PDB by default. We can then use the PDB commands to inspect and manipulate the program state.
We can see that breakpoint()
pauses the execution at the line where it is called. By using the commands we learned above, we can inspect the program state (p number
to see the value of n
) and step through the code line by line (s
).
One of the advantages of using breakpoint()
over pdb.set_trace()
is that we can switch to a different debugger at any time. By setting the PYTHONBREAKPOINT
environment variable, we can tell Python which debugger we would like to use.
For example, if we want to use IPython as our debugger, we can run:
$ export PYTHONBREAKPOINT=IPython.core.debugger.set_trace
$ python3 main.py
Another advantage of using breakpoint()
is that we can disable all breakpoints in our code by setting the PYTHONBREAKPOINT
environment variable to 0.
This is useful when we want to run our code without entering the debugger, without having to remove or comment out all the breakpoint()
statements.
$ export PYTHONBREAKPOINT=0
$ python3 main.py
breakpoint()
is a much-needed improvement over pdb.set_trace()
! It gives us the ability to quickly debug our code and easily switch between different debuggers.