Python debugger aliases
Tags: python, debugging, productivity
Published: 2020-11-12 10:41:56

---
title: Python debugger aliases
published: true
description: List of aliases for python debugger
tags: python, debugging, productivity
---

Hello all! In this note I'd like to collect some useful aliases which you can use in your [`.pdbrc`](https://github.com/python/cpython/blob/master/Lib/pdb.py#L55-L56) file which you can place to your HOME folder or current dir.

So let's start from docstring of pdb [module](https://github.com/python/cpython/blob/master/Lib/pdb.py#L42-44):
```
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.
```
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.
```


Pretty simple, right?
Yet so powerful. Next in documentation we ca see two examples:
```python
# 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
```

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](https://github.com/python/cpython/blob/master/Lib/pdb.py#L390) which accepts one argument -- `line`:
```python
...
        args = line.split()
        while args[0] in self.aliases:
            line = self.aliases[args[0]]
...
```
So what does first alias do?
Let's take a look. Write some code to provide a context to our tests.
```python
class Spam():
    """Run Away! Run Away!"""
    def __init__(self, ham):
        self.ham = ham

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


spam = Spam('ham')
spam.egg()
```
Run this file with `pdb3` command, see the `(Pdb)` prompt and enter `l` which stands for `list`:
```bash
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) 

```
Then enter `n` two times, so we will be here:
```python
(Pdb) l
  6  	    def egg(self):
  7  	        print(self.ham)
  8  	
  9  	
 10  	spam = Spam('ham')
 11  ->	spam.egg()
[EOF]
(Pdb) 
```

Now let's create an alias:
```
(Pdb) alias for k in %1.__dict__.keys(): print("{}.{} = {}".format(%1, k, %1.__dict__[k]))
```
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) 
```

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>
```

Yeah, much better!
Now let's try `ps spam`:
```
(Pdb) ps spam
*** NameError: name 'self' is not defined
```
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
```
And alias pi
```
(Pdb) alias pi
pi = for k in %1.__dict__.keys(): print("{}.{} = {}".format(%1, k, %1.__dict__[k]))
```
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>

```
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)
```
Now let's `restart`, `l`ist code, put `b`reakpoint at line `7` and `c`ontinue execution.
We end up at line 7 as we supposed to.
Let's check `ps`:
```
(Pdb) ps
<__main__.Spam object at 0x7fc94d49cef0>.ham = ham
```
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)
```
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))
```

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!