Faster and Easier Python Debugging with breakpoint() and PDB

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.

Using breakpoint()

When using PDB, we need to learn a couple 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.

CommandDescription
p [variable_name]Print the value of a expression.
n or nextContinue execution until the next line in the current function is reached or it returns.
s or stepExecute the current line, stop at the first possible occasion (either in a function that is called or in the current function).
c or continueContinue execution, only stop when a breakpoint is encountered.
l or listList source code for the current file.
h or helpPrints 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.

Open in new window

    Play with PDB by clicking green “Run” button near the top right of the code editor.

    If you want to navigate back to the code, click “Show files” at the top left of the code editor and select “main.py”.

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).

Changing the debugger behavior

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

End

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.