Ned Batchelder's updated .pdbrc

The great Ned Batchelder's .pdbrc has a bunch of helpful debugging aliases. Ned's post, from 2007, uses Python 2 syntax which I've updated and tweaked slightly below to use Python 3 syntax and the more modern f-strings for printing. F-strings were introduced in Python 3.6 so this won't work in earlier versions. If using Python <= 3.5, you can simply update Ned's original alias p_ line to place parantheses around the print statement.

# Ned's .pdbrc (updated)

# Print a dictionary, sorted. %1 is the dict, %2 is the prefix for the names.
alias p_ for k in sorted(%1.keys()): print(f"%2{k.ljust(max(len(s) for s in %1.keys()))} = {%1[k]}")

# Print the member variables of a thing.
alias pi p_ %1.__dict__ %1.

# Print the member variables of self.
alias ps pi self

# Print the locals.
alias pl p_ locals() local:

# Next and list, and step and list.
alias nl n;;l
alias sl s;;l

I'll walk through how the aliases work at the end, but first check out a few examples. Place the above at ~/.pdbrc and try them out.

In [1]:
def add(a, b):
    breakpoint()
    return a + b

result = add(1, 2)
> <ipython-input-1-2349524cd9c1>(3)add()
-> return a + b
local:a = 1
local:b = 2
In [2]:
from random import uniform
from types import SimpleNamespace

def random_lat_long():
    lat_ = uniform(-90, 90)
    long_ = uniform(-180, 180)
    lat_long = SimpleNamespace(latitude=lat_, longitude=long_)
    breakpoint()
    return lat_long

lat_long = random_lat_long()
> <ipython-input-2-5552f62a4e0e>(9)random_lat_long()
-> return lat_long
lat_long.latitude  = -52.22043390873424
lat_long.longitude = -97.64413970548634
In [3]:
class AwesomeNumbers:

    def __init__(self):
        self.pi = 3.14
        self.eulers_number = 2.718
        breakpoint()

awesome_numbers = AwesomeNumbers()
--Return--
> <ipython-input-3-4594f993075e>(6)__init__()->None
-> breakpoint()
self.eulers_number = 2.718
self.pi            = 3.14

How it works

Most of the heavy lifting is done in the alias p_ line. Here's a function that does the same thing, but broken up for readability.

In [4]:
def p_(dict_, prefix):
    # find the length of the longest key in the dictionary
    max_key_len = max(len(key) for key in dict_.keys())

    for key in sorted(dict_.keys()):
        value = dict_[key]
        # adjust width of the key's string to the length of
        # the longest key
        key_repr = key.ljust(max_key_len)
        value = dict_[key]
        print(f"{prefix}{key_repr} = {value}")

awesome_numbers = {"pi": 3.14, "eulers_number": 2.718}
p_(dict_=awesome_numbers, prefix="awesome_numbers.")
awesome_numbers.eulers_number = 2.718
awesome_numbers.pi            = 3.14

To build on this, the pl alias calls p_ with the handy locals() built-in, which stores a dictionary containing variables on the stack.

The pi and ps command build on p_ by passing an object's __dict__ attribute which stores the object's named attributes. For example

In [5]:
awesome_numbers = SimpleNamespace(pi=3.14, eulers_number=2.718)
p_(awesome_numbers.__dict__, "awesome_numbers.")
awesome_numbers.eulers_number = 2.718
awesome_numbers.pi            = 3.14

Changes from Ned's pdbrc

The only change that I've made is from this line in Ned's .pdbrc

alias p_ for k in sorted(%1.keys()): print "%s%-15s= %-80.80s" % ("%2",k,repr(%1[k]))

This line uses Python 2's print syntax, but it also uses the older % formatting to fix the width of the key's name to a constant 15-characters. A modest improvement is to instead fix the width of the key names to the length of the longest key, making it slightly more readable if all of the key names are very short or if there any keys longer than 15 characters. This comes at the expense of the readability of our .pdbrc which now has an almost incomprehensible alias p_ line.

alias p_ for k in sorted(%1.keys()): print(f"%2{k.ljust(max(len(s) for s in %1.keys()))} = {%1[k]}")

I'd encourage you to try out aliases for tasks that you find yourself doing repeatedly while debugging. I find myself looking for the pid of my Python process often enough that I also use

alias pid import os; os.getpid()

The inspect module could probably form the basis for a bunch of interesting aliases.