Let's use Python's breakpoint
function to diagnose a problem in our Python code.
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.
breakpoint
to launch the Python debuggerLet's try to investigate what's going on by using a Python built-in function: the 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 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.
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:
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.
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.
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.
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.
Need to fill-in gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.
Sign in to your Python Morsels account to track your progress.
Don't have an account yet? Sign up here.