Debugging Python without an IDE – pdb

Click here for the sequel of this post

Most of my work at Arbisoft involves developing and debugging custom features for Open edX based systems.

Most of my colleagues here prefer to use PyCharm as their default IDE and editor. PyCharm is among the best Python development tools out there, however, I prefer to use Sublime Text 3 in my workflow so I have to compromise on most of the tooling that PyCharm provides.

One of the most important tools is the debugger, and I’ve had to find alternatives to PyCharm’s debugger. In this post, I’ll give a demonstration of a few tools we can use to debug Python code without relying on an IDE. First on our list is:

pdb – The Python Debugger

pdb ships with every python installation and requires zero third party tooling to get working. We’ll learn how to use it with a small walk-through.

Consider the following code:

def foo(bar=[]):
    bar.append("baz")
    return bar

def test1():
    response = foo()
    response = foo()
    return response

What do you think test1() will return?

I thought that it would simply return an array with a single element i.e "baz"

['baz']

But instead it returns an array with two elements, both "baz"

['baz', 'baz']

So, let’s debug this using pdb. pdb comes installed with Python. You can set a breakpoint in your code using the following statement

import pdb; pdb.set_trace()

So, to debug the above code, let’s add a breakpoint using pdb in the test1() function

def test1():
    import pdb;pdb.set_trace()
    response = foo()
    response = foo()
    return response

We can run this code and we will see that pdb will stop the script at the intended line

~/workspace/test$ python python.py
-> response = foo()
(Pdb)

Now I can interact with the code by typing commands into the shell. Some of the commands I use are

  • n (next)  – Continues execution until the next line in the current function is reached or it returns
  • s (step) – Executes the current line and stop at the first possible occasion. This is equivalent to step into on most GUI based debuggers
  • c (continue) – Continues execution and stops only when another breakpoint is encountered.
  • p (print) – Takes an expression and prints its output
  • l (list) – Lists the source code of the current file

Let us use these commands to see the flow of our test1() function

Debugging using pdb

As you can see in the video above, I step into the second call of foo and check the value of bar which turns out to be ['baz'] instead of the initial value [].

The reason for this is a little tricky: The default values passed to function arguments are evaluated only once in Python. 

Meaning that the expressions [] is evaluated as an empty list and the reference of that list assigned to bar. So, when foo() is called again, the same reference is stored in bar which results in 'baz' being appended to the same list. Hence we get ['baz', 'baz'] in response.

To correct this situation, we can modify code as under:

def foo(bar=None):
    if bar is None:
        bar = []
    bar.append('baz')
    return bar

Now on every call of foo() a new empty list [] will be assigned to bar making sure that our code works as expected.

Conclusion

I realize that pdb is not an exact replace for PyCharm but in most situations it gets the job done. Also, one advantage of pdb being a terminal based debugger is that I can use it in any environment even virtualized environments like Docker and Vagrant and remote environments through SSH. 

You can read more about how to use pdb and all features it provides in the official Python documentation here.

I hope I’ve done a decent enough explanation on how to use pdb. If you have any questions feel free to drop a message and I’ll try to answer them.

Cheers!