Master pdb: Python's Built-in Debugger
Learn to use pdb, Python's standard library debugger, including breakpoints, post-mortem debugging, and essential commands to replace print-based debugging.
The Debugger You Already Have: pdb in Python
Every Python developer hits that moment when print() statements just aren't cutting it anymore. You're staring at a traceback that makes no sense, or your code works fine until that one specific input breaks everything. The natural instinct is to sprinkle print() everywhere, but there's a better tool sitting right there in Python's standard library: pdb, the Python Debugger.
I remember spending an entire afternoon debugging a Django application at Pythonskillset, going through dozens of iterations of print statements, only to discover I was modifying a list that was being shared across function calls. pdb would've caught that in minutes.
What Makes pdb Different?
Think of pdb as a pause button for your code. Instead of guessing what your variables contain at certain points, you can freeze execution, inspect anything, and step through line by line. It's like having x-ray vision into your running program.
Getting Started With pdb
The simplest way to use pdb is to drop a breakpoint right where you want to investigate. There are two flavors:
# Old school (works in Python 2 and 3)
import pdb; pdb.set_trace()
# Modern Python (3.7+)
breakpoint()
The breakpoint() function is cleaner and respects the PYTHONBREAKPOINT environment variable, which means you can disable all breakpoints without changing code.
The Really Useful Commands
Once pdb stops your code, you get a (Pdb) prompt. Here's what actually matters:
n (next) - Executes the current line and moves to the next one in the same function. This is your bread and butter.
s (step) - Steps into function calls. If you're on a line that calls another function, s takes you inside it.
c (continue) - Runs the program until the next breakpoint. Perfect when you've seen enough of this section.
l (list) - Shows where you are in the code with context. The current line is marked with ->.
p (print) - Shows the value of any expression. p my_variable, p len(data), whatever you need.
pp (pretty print) - Same as p but formats complex objects like dictionaries and lists.
q (quit) - Exits the debugger and terminates the program.
A Real Example
Let me show you something that actually happened during a Pythonskillset code review:
def calculate_averages(scores):
total = 0
for student in scores:
total += sum(student['grades'])
return total / len(scores)
This looks innocent enough, but it crashes when a student has no grades. With pdb:
def calculate_averages(scores):
total = 0
breakpoint() # Stop right here
for student in scores:
total += sum(student['grades'])
return total / len(scores)
Now you can inspect scores, check if any student is missing the 'grades' key, and understand exactly what's going wrong. You can even type len(student['grades']) at the pdb prompt to see what happens.
The Hidden Gem: Post-Mortem Debugging
This is my favorite pdb feature. When your code crashes, instead of just staring at the traceback:
try:
run_risky_operation()
except:
import pdb; pdb.post_mortem()
This drops you into debugger mode right at the crash site. All your variables are preserved exactly as they were when the exception happened. It's like being a detective who arrives at the crime scene before anyone touches anything.
Setting Breakpoints Strategically
Don't just put breakpoints everywhere. Think about what you're trying to discover:
- At function start - To check if arguments are what you expect
- Before an exception - Right before a line that keeps erroring
- In loop iterations - To see if data changes across iterations
- At conditional branches - To verify your if/else logic
The Trade-offs
pdb slows things down, obviously. You're not going to debug a production web server with it. But for development and testing, it's invaluable. The biggest advantage over print statements is that you're not committing debug code or dealing with the mess of removing it later.
Beyond the Basics
Once you're comfortable, explore these:
- Conditional breakpoints:
breakpoint()inside anifstatement - Watchpoints: Monitor when a variable changes value
- Remote debugging: pdb over network connections for headless systems
pdb isn't flashy, but it's been solving real debugging problems for decades. Next time you reach for print(), consider whether pdb might save you more time. At Pythonskillset, we've found that developers who learn pdb early spend less time debugging and more time building.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.