DEV Community

loading...

Python debugger aliases

Alex Bender
there is no doubt in my mind that interfaces could be better.
・5 min read

Hello all! In this note I'd like to collect some useful aliases which you can use in your .pdbrc file which you can place to your HOME folder or current dir.

So let's start from docstring of pdb module:

The debugger supports aliases, which can save typing. And
aliases can have parameters (see the alias help entry) which
allows one a certain level of adaptability to the context
under examination.
Enter fullscreen mode Exit fullscreen mode

So what is an alias after all?

 alias [name [command]]

    Create an alias called _name_ that executes _command_. The 
command must not be enclosed in quotes. Replaceable 
parameters can be indicated by %1, %2, and so on, while %* is 
replaced by all the parameters. If no command is given, the 
current alias for name is shown. If no arguments are given, 
all aliases are listed.

    Aliases may be nested and can contain anything that can 
be legally typed at the pdb prompt. Note that internal pdb 
commands can be overridden by aliases. Such a command is then
 hidden until the alias is removed. Aliasing is recursively
 applied to the first word of the command line; all other 
words in the line are left alone.
Enter fullscreen mode Exit fullscreen mode

Pretty simple, right?
Yet so powerful. Next in documentation we ca see two examples:

# Print instance variables (usage "pi classInst")
alias pi for k in %1.__dict__.keys(): print("%1.",k,"=",%1.__dict__[k])
# Print instance variables in self
alias ps pi self
Enter fullscreen mode Exit fullscreen mode

The second one shows that aliases could be recursively applied to the first word of the command line meaning that alias ps pi self will be expanded further. Each alias handled in Pdb.precmd method which accepts one argument -- line:

...
        args = line.split()
        while args[0] in self.aliases:
            line = self.aliases[args[0]]
...
Enter fullscreen mode Exit fullscreen mode

So what does first alias do?
Let's take a look. Write some code to provide a context to our tests.

class Spam():
    """Run Away! Run Away!"""
    def __init__(self, ham):
        self.ham = ham

    def egg(self):
        print(self.ham)


spam = Spam('ham')
spam.egg()
Enter fullscreen mode Exit fullscreen mode

Run this file with pdb3 command, see the (Pdb) prompt and enter l which stands for list:

pdb3 /tmp/spam.py 
> /tmp/spam.py(1)<module>()
-> class Spam():
(Pdb) l
  1  -> class Spam():
  2         """Run Away! Run Away!"""
  3         def __init__(self, ham):
  4             self.ham = ham
  5     
  6         def egg(self):
  7             print(self.ham)
  8     
  9     
 10     spam = Spam('ham')
 11     spam.egg()
(Pdb) 

Enter fullscreen mode Exit fullscreen mode

Then enter n two times, so we will be here:

(Pdb) l
  6         def egg(self):
  7             print(self.ham)
  8     
  9     
 10     spam = Spam('ham')
 11  -> spam.egg()
[EOF]
(Pdb) 
Enter fullscreen mode Exit fullscreen mode

Now let's create an alias:

(Pdb) alias for k in %1.__dict__.keys(): print("{}.{} = {}".format(%1, k, %1.__dict__[k]))
Enter fullscreen mode Exit fullscreen mode

It's a slightly changed version of the alias from the documentation.
Now let's use it:

(Pdb) pi spam
<__main__.Spam object at 0x7effec0f3358>.ham = ham
(Pdb) 
Enter fullscreen mode Exit fullscreen mode

Hmm.. not much...
Let's try this:

(Pdb) pi Spam
<class '__main__.Spam'>.__module__ = __main__
<class '__main__.Spam'>.__doc__ = Run Away! Run Away!
<class '__main__.Spam'>.__init__ = <function Spam.__init__ at 0x7fd414c622f0>
<class '__main__.Spam'>.egg = <function Spam.egg at 0x7fd414c62268>
<class '__main__.Spam'>.__dict__ = <attribute '__dict__' of 'Spam' objects>
<class '__main__.Spam'>.__weakref__ = <attribute '__weakref__' of 'Spam' objects>
Enter fullscreen mode Exit fullscreen mode

Yeah, much better!
Now let's try ps spam:

(Pdb) ps spam
*** NameError: name 'self' is not defined
Enter fullscreen mode Exit fullscreen mode

What do we have here is an usage of the alias out of context.
Let's check alias again to refresh it in mind:

(Pdb) alias ps
ps = pi self
Enter fullscreen mode Exit fullscreen mode

And alias pi

(Pdb) alias pi
pi = for k in %1.__dict__.keys(): print("{}.{} = {}".format(%1, k, %1.__dict__[k]))
Enter fullscreen mode Exit fullscreen mode

So pi accepts one argument: %1, and ps tries to pass self to that alias and fails because self is not in context. Let's define it and try again:

(Pdb) self = Spam
(Pdb) ps
<class '__main__.Spam'>.__module__ = __main__
<class '__main__.Spam'>.__doc__ = Run Away! Run Away!
<class '__main__.Spam'>.__init__ = <function Spam.__init__ at 0x7fdbc40f1e18>
<class '__main__.Spam'>.egg = <function Spam.egg at 0x7fdbc40f1d90>
<class '__main__.Spam'>.__dict__ = <attribute '__dict__' of 'Spam' objects>
<class '__main__.Spam'>.__weakref__ = <attribute '__weakref__' of 'Spam' objects>

Enter fullscreen mode Exit fullscreen mode

We cheated a bit here. As described in the documentation aliases are treated as commands typed directly into the prompt meaning that all needed ingredients must be prepared to digest. You can treat aliases as functions which accepts n variables which would be taken from locals or globals.
And where variable self is usually defined? In the class methods.
Let's restart our session with restart command (You can check all available commands typing help into prompt).
You don't need worry about lost progress because restart will handle everything automatically: all breakpoints and defined aliases will be there at your service.

(Pdb) restart
Restarting /tmp/spam.py with arguments:
    /tmp/spam.py
> /tmp/spam.py(1)<module>()
-> class Spam():
(Pdb) l
  1  -> class Spam():
  2         """Run Away! Run Away!"""
  3         def __init__(self, ham):
  4             self.ham = ham
  5     
  6         def egg(self):
  7             print(self.ham)
  8     
  9     
 10     spam = Spam('ham')
 11     spam.egg()
(Pdb) b 7
Breakpoint 1 at /tmp/spam.py:7
(Pdb) c
> /tmp/spam.py(7)egg()
-> print(self.ham)
Enter fullscreen mode Exit fullscreen mode

Now let's restart, list code, put breakpoint at line 7 and continue execution.
We end up at line 7 as we supposed to.
Let's check ps:

(Pdb) ps
<__main__.Spam object at 0x7fc94d49cef0>.ham = ham
Enter fullscreen mode Exit fullscreen mode

We do not get errors because self is already defined.


That was short demo of aliases usage in pdb.

Be aware that aliases are executed in the current context so they can clutter with your variables:

  6 B->     def egg(self):
  7             print(self.ham)
(Pdb) aaa=1
(Pdb) bbb=2
(Pdb) alias clutter aaa=bbb
(Pdb) locals()['aaa'], locals()['bbb']
(1,2)
(Pdb) clutter
(Pdb) locals()['aaa'], locals()['bbb']
(2,2)
Enter fullscreen mode Exit fullscreen mode

After executing clutter alias variable aaa got new value. So be sure to use variable names which are unique, prefix them with ___ as an example.

And I've got some more to share:

kf = __tmp = dir(%1); __filter = [k for k in __tmp if '%2' in k]; print(__filter); del __tmp; del __filter
pi = for k in %1.__dict__.keys(): print("{}.{} = {}".format(%1, k, %1.__dict__[k]))
ps = pi self
source = import inspect; print(inspect.getsource(%1))
Enter fullscreen mode Exit fullscreen mode

Please share your snippets if you have anything useful, let's learn together. Debugging is pretty important skill to master so do not hesitate to read documentation on Pdb and source code.
Thanks for reading!

Discussion (2)

Collapse
Sloan, the sloth mascot
Comment deleted
Collapse
alexbender profile image
Alex Bender Author

Hello gravesli! Thanks for your comment. I've checked the repo and it looks good, but I'm not gonna review it because your messages you're bombarding a lot of users looks really not cool. Number of start you've got is not quality of your code. You're not your stars. You're not your code. I can only suggest you reading more documentation and source code of others. Thanks