Debugging with Python's breakpoint

Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
6 min. read 4 min. video Python 3.7—3.11
Share
Copied to clipboard.
Tags
Python Morsels
Watch as video
04:22

Let's use the Python debugger to diagnose a problem in our Python code.

A broken Python program

Here we have a program called guess.py:

from random import randint

answer = randint(0, 1)
n = input("Guess: 0 or 1? ")
if n == answer:
    print("Correct!")
else:
    print(f"Incorrect. The answer was {answer}.")

This program prompts the user to guess either 0 or 1, and then it prints out either Correct or Incorrect, depending on whether the guess was correct:

$ python3 guess.py
Guess: 0 or 1? 0
Incorrect. The answer was 1.

But there's a bug in our code right now! Even the user's guess is correct, our program prints Incorrect:

$ python3 guess.py
Guess: 0 or 1? 0
Incorrect. The answer was 0.

Using breakpoint to launch the Python debugger

Let's try to investigate what's going on by using the built-in breakpoint function. The breakpoint function starts the Python debugger.

We're going to add a breakpoint call just before where we think the bug might be in our code:

from random import randint

breakpoint()
answer = randint(0, 1)
n = input("Guess: 0 or 1? ")
if n == answer:
    print("Correct!")
else:
    print(f"Incorrect. The answer was {answer}.")

Now when we run our code again, we'll see a Pdb prompt. This is the Python debugger prompt:

$ python3 guess.py
> /home/trey/guess.py(4)<module>()
-> answer = randint(0, 1)
(Pdb)

Python has put our program on pause, and is waiting for us to type some commands. We can also see which line of code is about to be run next (the line answer = randint(0, 1)).

This is similar to the Python REPL, but it's not quite the same as the Python REPL.

PDB is not a Python REPL

Like the Python REPL, in PDB we can type lines of code and see the result of running that line of code:

(Pdb) randint(0, 1)
1

But, unlike the regular Python REPL, we can't span our code over multiple lines:

(Pdb) randint(
*** SyntaxError: '(' was never closed

And some valid Python code doesn't work because it collides with a Pdb command:

(Pdb) help(randint)
*** No help for `(randint)`
(Pdb)

The Python Debugger thought we were trying to use the PDB help command, but we were actually trying to use Python's built-in help function.

By typing help at in PDB we can see many of the commands that PDB supports:

(Pdb) help

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

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

Let's talk about some of these commands.

PDB commands: listing and moving to the next line

The PDB l command lists the current file that we're in and our current line within that file:

(Pdb) l
  1     from random import randint
  2
  3     breakpoint()
  4  -> answer = randint(0, 1)
  5     n = input("Guess: 0 or 1? ")
  6     if n == answer:
  7         print("Correct!")
  8     else:
  9         print(f"Incorrect. The answer was {answer}.")
[EOF]

The n command runs the next line of code, which in our case would be line 4 (that answer = line):

(Pdb) n
> /home/trey/guess.py(5)<module>()
-> n = input("Guess: 0 or 1? ")

We've just assigned an answer variable, which is now 1.

(Pdb) answer
1

If we use the n command again, we'll go to the next line which will prompt us to enter some input:

(Pdb) n
Guess: 0 or 1?

Let's enter 1:

(Pdb) n
Guess: 0 or 1? 1
> /home/trey/guess.py(6)<module>()
-> if n == answer:

Running Python code in PDB

At this point, if we tried to look at answer, it works:

(Pdb) answer
1

But if we tried to look at the variable n we would have a problem:

(Pdb) n

If we type n and we hit Enter, PDB would go to the next line of code because n is a valid PDB command.

To avoid this conflict between valid Python code and valid Pdb commands, we can use an exclamation mark (!) before our code.

(Pdb) !n
'1'

Before when we used help on randint and we were trying to use the built-in help function. We could have added an exclamation mark before our command to tell PDB that this was actually Python code that we were trying to run.

(Pdb) !help(randint)

Help on method randint in module random:

randint(a, b) method of random.Random instance
    Return random integer in range [a, b], including both end points.
(END)

That exclamation mark forces PDB to interpret everything after the exclamation mark as just another line of Python code:

The other way we could fix this problem is to run the interact command:

(Pdb) interact
*interactive*
>>>

PDB's interact command will launch a Python REPL.

Just as in any other Python REPL, we can type Python commands and see the result:

>>> answer
1
>>> n
'1'

Here we can see that answer is an integer, but n is a string.

>>> type(answer)
<class 'int'>
>>> type(n)
<class 'str'>

Which means n == answer (which is what our code is just about to check) returns False:

>>> n == answer
False

That's the bug in our code!

PDB can help you debug your code, and one of the most helpful commands within Pdb is interact, which launches a regular Python REPL.

Fixing the bug

Let's exit the Python REPL and Pdb and fix that bug in our code:

We need to convert n (which is currently a string) to an integer so we can properly compare the n and answer numbers:

from random import randint

answer = randint(0, 1)
n = int(input("Guess: 0 or 1? "))
if n == answer:
    print("Correct!")
else:
    print(f"Incorrect. The answer was {answer}.")

It looks like we've fixed the bug in our code:

$ python3 guess.py
Guess: 0 or 1? 0
Correct!

We finally see Correct! printed out for a correct guess.

Useful PDB commands

When I use PDB to debug my Python code, I pretty much only use these commands:

PDB Command Action
n or next Run the next line of code
s or step Step into current line (usually a function call)
r or return Return from the current function
l or list List the surrounding code lines
interact Starts an interactive Python interpreter (REPL)
c or continue Continue running (until a breakpoint or exit)
b or break Set a breakpoint for a specific line or function
! Special prefix to say "run this as Python code"
pp Pretty-print the value of a Python expression

I mostly use n to go to the next line, and l to list the lines around my current line. I also use interact or ! to force PDB to interpret the Python code I'm running as actual Python code.

Some of these other commands are occasionally useful, but you get a lot done of debugging done with just a few PDB commands.

Summary

Using print to debug your Python code does work, but it can sometimes be tedious.

To start an interactive Python interpreter from right within your program, you can use the built-in breakpoint function to launch the Python debugger (a.k.a. PDB), and you can use the interact command within PDB to launch a regular Python REPL.

A Python Tip Every Week

Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.

Python Morsels
Watch as video
04:22