Debugging your code in Python — pdb vs. rpdb
By: Fernando Diaz
Tags: Testing, Python, Software Testing, Debugging, Programming

Debugging is an important part of resolving programming problems. There are many tools out there that are much better than print statements, although this is a debated topic.

Photo by Yoal Desurmont on Unsplash

Python comes with a built-in interactive debugger known as pdb.

Pdb allows you to set breakpoints and single step at the source line level, inspect stack frames, list source code, and evaluate arbitrary Python code in the context of any stack frame. Post-mortem debugging can also be performed under program control.

Then there’s rpdb, a remote debugger based on pdb. It re-routes stdin and stdout to a socket handler, so that you can debug server processes.

When do I use pdb over rpdb?

Pdb should be used in local applications, whereas rpdb runs remotely and can be used to debug web frameworks like Flask. With rpdb installed, you can start the server and run through the server code remotely.

Below I’ll show you both pdb and rpdb running in some simple applications, one which is running as a script, and one which starts a Flask server. Be sure to clone the Debugging in Python Repo to run the examples in this tutorial.

$ git clone https://github.com/diazjf/learn-pydebug Cloning into 'learn-pydebug'...

remote: Enumerating objects: 18, done.

remote: Counting objects: 100% (18/18), done.

remote: Compressing objects: 100% (14/14), done.

remote: Total 18 (delta 0), reused 15 (delta 0), pack-reused 0

Receiving objects: 100% (18/18), 103.60 KiB | 573.00 KiB/s, done. $ cd learn-pydebug

Note: The Debugging in Python Repo has information on installing and running applications which include and show off pdb and rpdb. The code has comments on where the issue is and how debugging can help.

Using pdb

We add the following code to where in the application we want to start pdb. The code in pdb/main.py contains the following line:

import pdb; pdb.set_trace()

When running the python application, pdb will automatically start when that pdb.set_trace() is hit.

$ python pdb/main.py

Enter some words: I am a cat > /Users/fernandodiaz/Desktop/debugging/pdb/main.py(23)<module>()

-> num_chars = count(input)

(Pdb)

you’ll see that pdb is initiated. Pdb contains the following basic functions, which can be run at the prompt. Here are some examples of the output from pdb after some commands are run.

l(ist): Lists the lines surrounding the current line

(Pdb) l 18 input = input("Enter some words: ")

19

20 import pdb; pdb.set_trace()

21

22 # count the number if characters

23 -> num_chars = count(input)

24 print("Number of Characters: %s" % num_chars)

w(here): Displays the file and line number where we currently are

(Pdb) w > /Users/fernandodiaz/Desktop/learn-pydebug/pdb/main.py(23)<module>()

-> num_chars = count(input)

s(tep): Step into the function at the current line

(Pdb) s --Call--

> /Users/fernandodiaz/Desktop/learn-pydebug/pdb/main.py(3)count()

-> def count(message=""):

n(ext): Execute the current line

(Pdb) n > /Users/fernandodiaz/Desktop/learn-pydebug/pdb/main.py(4)count()

-> msg = message.split()

a(rgs): Print the argument list of the current function

(Pdb) a message = 'I am a cat'

p(rint) <name>: Print value of <name>

(Pdb) n > /Users/fernandodiaz/Desktop/learn-pydebug/pdb/main.py(9)count()

-> if len(msg) > 3: (Pdb) p msg ['I', 'am', 'a', 'cat']

Note: I ran all these commands in order on the pdb testing application.

By running these commands we can continue to go to the next lines in the count function until we see something wrong. In this example we can see that

if len(msg) > 3:

return “TOO MANY WORDS, TRY AGAIN”

This shows is that we either need to add more details to the message to let users know what is acceptable or we can adjust the number of words to be number of chars instead. No matter how we solve it, we can now tell where the issue is.

Photo by Sigmund on Unsplash

Although it may not seem that impressive here, pdb is really useful in large codebases where there is alot going on.

Using rpdb

We add the following to where in the application we want to start rpdb:

import rpdb; rpdb.set_trace()

Note: rpdb must be installed for this command to be recognized. It is installed in the prerequisites.

If you take a look at rpdp/remote/routes.py, you can see that the debugger was added to the /count route. We can run the Flask web-server by installing the prerequisites and then running:

$ python rpdb/run.py

* Environment: production

WARNING: This is a development server. Do not use it in a production deployment.

Use a production WSGI server instead.

* Debug mode: on

* Running on all addresses.

WARNING: This is a development server. Do not use it in a production deployment.

* Running on

* Restarting with stat

* Debugger is active!

* Debugger PIN: 926-264-267 * Serving Flask app 'remote' (lazy loading)* Environment: productionWARNING: This is a development server. Do not use it in a production deployment.Use a production WSGI server instead.* Debug mode: on* Running on all addresses.WARNING: This is a development server. Do not use it in a production deployment.* Running on http://192.168.1.147:5000/ (Press CTRL+C to quit)* Restarting with stat* Debugger is active!* Debugger PIN: 926-264-267

In a separate terminal we can send a request to the endpoint the application is running on.

-d '{"message": "This is a message"}'

$ curl -X POST http://192.168.1.147:5000/count -d '{"message": "This is a message"}'

Here we are sending a message that originally caused the server to throw a 400. With the debugger present, this request causes the application to hang and allows us to go through the debugger and see what’s going on. We can do this by running the following in a separate terminal:

$ nc 127.0.0.1 4444 > /Users/fernandodiaz/Desktop/debugging/rpdb/remote/routes.py(15)add_note()

-> if not msg:

(Pdb)

Now we can run commands just like we did with pdb in order to determine why we get 400s with our message.