Posted on by & filed under debugging, programming, python, Tech, testing.

All developers know finding and squashing bugs comes with the territory of writing sane, stable, and performant software. On a good day, debugging can be trivial, but we’ve all been there: running tests and reading print statements at 11pm and nearly losing our minds over vanishing User objects or differing behavior between two servers. Sometimes the rudimentary approach of logging debug statements or peppering prints throughout the code works well enough, but there are better ways to inspect your app during runtime.

pdb, the Python Debugger, is an interactive debugger that is part of the Python standard library. Powerful in action and straightforward in use, pdb should be considered an essential part of your regular debugging workflow. pdb allows you to jump into a shell at arbitrary breakpoints in your code, where you can print variables and objects, step through the code line by line, change the values of objects on the fly, and more.

Even better than pdb is ipdb, a third-party replacement that retains all of pdb’s functionality and adds IPython support for the interactive shell, like tab completion, object introspection, friendlier tracebacks, color support, and a bucket of magic functions (which are out of the scope of this article). The bottom line is that you can use ipdb just like pdb, but your user experience will be much nicer. Simply $ pip install ipdb to install this package.

In this post, I’ll explain a few pdb commands to get you started right away, show some basic tricks to increase your levels of happiness, and include a short example session to illustrate a real-world debugging experience which could have been maddening but was made trivial by using this tool. Of course, there is much more to pdb (and IPython). Consider this an introduction.

Getting started with debuggers

To set your breakpoints, add import ipdb; ipdb.set_trace() where you want to jump into the debugger. Once you reach a breakpoint, you’ll be given an interactive shell and a few lines of code around your breakpoint for context.

Essential commands

Most of pdb’s commands have one-letter aliases. Pressing enter will generally repeat your last command.


n simply continues program execution to the next line in the current function. This will be your most-typed command.


s steps to the very next line of executable code, whether it’s inside a called method or just on the next line. I think of this as “step into” for tunneling into a function or method.


c continues program execution until another breakpoint is hit.

l[ist] [first[, last]]

l lists a larger portion of code–11 lines–moving down the file with successive calls. You can optionally provide a line number to center the view on, as well as a second number for a range.

p[rint] and pp

p and pp are print and pretty-print. pp uses the pprint module. I use pp almost exclusively.


a is one of my favorites. It prints out all the arguments the current function received.

j[ump] line_number

j will jump to the given line of code, actually skipping the execution of anything between.

Tips for effective pdb use

pp locals() and pp globals()

Use these commands to see all variables in the given scope, local or global. This is super useful for a quick glance at all the objects your program has access to at that moment.

Change variables

You can change the value of variables while stepping through your code. Know the current value of doodad.widget will crash the program and want to test an alternative? Change that widget! Want to see what a response.status_code of 418 from an external API would mean for your website? Change that status_code!

Use tab completion and introspection

This is specific to ipdb users, since pdb doesn’t have this functionality. Tab completion has become second-nature for me. It’s incredibly useful when you’re working with objects you don’t really know about. Say you notice a thing object after pp locals() and become curious about it. Simply typing out thing. and pressing tab will list all the properties and methods that belong to thing, and it will continue to autocomplete as you type more letters.

Using ipdb during testing

Testing is amazing, and everybody should be doing it. Even so, sometimes your test results can get wacky, making them a wonderful candidate for dropping into ipdb. A couple days ago, one of my tests was passing on its own but failing during the whole suite, an experience which can lead to much gnashing of teeth. But with some careful debugging, I was able to find the issue and bandaid it before too long.



Dropped set_trace() in and running tests again….

This is where I stopped and said “huh?”. Who are those two random-looking users? The test users I created have names. Where did these extras come from?

My next step was to plant a set_trace() on the very first line of my test class’ setUp() method and immediately query for all users. I’m going to omit that process for the sake of space, but sure enough, <User: 53ed1fb> and <User: 4fd1a18> existed before my class even set itself up.

So I’ve already found two rogue users in my test and determined that they’re being created before my test runs. I now know that I can bandaid my problem by deleting all users at the top of setUp() while I search for the real issue, likely a setUpClass() without a correctly implemented tearDownClass().

The problem is patched, tests are green, we can deploy, we have a good clue about where to look next for the real solution, and we can go on with our lives. Thanks, pdb!

Tags: bandaids, debugging, ipdb, mysterious users, pdb, Python, testing,

Comments are closed.