'How to change IPython %pdb and %debug debugger?

By default, ipython uses ipdb as debugger with %pdb or %debug magics. However, I much prefer pdb++... Is there a way of changing the debugger called with these magics ? (I am aware I can simply use pdb.xpm() on exception with pdb++, but I'd like to make it work with ipython magic commands so that I don't have to wrap the code each time...)



Solution 1:[1]

So at least for limited circumstances and not in a way I'd necessarily recommend, the answer here is yes. I can't promise the below will work outside the confines of what I did, but it might give you enough insight to play around with it yourself. Caution is warranted because it involves changing undocumented attributes of the ipython shell class at runtime. TLDR: I hunted down how ipython calls the debugger when the %pdb magic is on or when you call the %debug magic, and I updated it to use the debugger I wanted. Skip the next two paragraphs if you just want the approach that worked for me and don't care about the hunt.

Long version: when you run ipython it starts an instance of TerminalInteractiveShell, which has a debugger_cls attribute telling you the debugger that ipython will launch. Unfortunately, at the level of TerminalInteractiveShell, debugger_cls is actually a property of the class, and has no setter that lets you modify it. Rather, it either gets set to Pdb (actually a more featureful ipython Pdb than the traditional pdb) or TerminalPdb (even more features).

If you dig deeper, however, you find that debugger_cls gets passed up to InteractiveShell to initialize how tracebacks are handled. There it seems to disappear into the initialization of InteractiveShell's InteractiveTB property, but actually just ends up as the debugger_cls attribute of that (InteractiveTB) class (by setting the inherited attribute from TBTools). Finally, this debugger_cls attribute only gets used to set the pdb attribute (more or less by doing TBToolsInstance.pdb = TBToolsInstance.debugger_cls()) in one of several places. In any case, it turns out that these attributes can be changed! And if you change them correctly they will percolate to the shell itself! Importantly, this relies on the fact that ipython makes use of the Traitlets package to create a Singleton object for the shell, and this allows you to gain access to that object from within the terminal itself. More on that below.

Below I show the code you can run in the ipython shell to achieve your desired result. As an example, I'm replacing the default debugger (TerminalPdb) with a modified version I created that deals more nicely with certain list comprehensions (LcTerminalPdb). The process (which you can run in the ipython shell) is as follows.

# import the TerminalInteractiveShell class
from IPython.terminal.interactiveshell import TerminalInteractiveShell
# grab the specific instance of the already initialized ipython
shl = TerminalInteractiveShell().instance()
# grab the InteractiveTB attribute (which is a class)
tbClass = shl.InteractiveTB
# load the debugger class you want to use; I assume it's accessible on your path
from LcTerminalPdb import LcTerminalPdb
# change tbClass's debugger_cls to the debugger you want (for consistency 
# with the next line)
tbClass.debugger_cls = LcTerminalPdb
# more importantly, set the pdb attribute to an instance of the class
tbClass.pdb = tbClass.debugger_cls()
# The above line is necessary if you already have the terminal running 
# (and have entered pdb at least once); otherwise, ipython will run it on 
# its own

That's it! Note that because you call the instance() method of TerminalInteractiveShell, you are grabbing the object for the currently running shell, which is why the modifications will affect the shell itself and so all following debugs. For a bonus, you can add these lines of code to your ipython_config.py file, so the debugger you want (LcTerminalPdb here) is always loaded with ipython:

c.InteractiveShellApp.exec_lines = [
'%pdb on',
'from LcTerminalPdb import LcTerminalPdb',
'from IPython.terminal.interactiveshell import TerminalInteractiveShell',
'shl = TerminalInteractiveShell().instance().InteractiveTB',
'shl.debugger_cls = LcTerminalPdb',
]

Note that above I don't need to write the extra shl.pdb = shl.debugger_cls() line, as ipython will take care of it the first time a debug point is entered. But feel free to, to be sure.

NOTES:

  • I have only tested this using LcTerminalPdb, and only briefly, but it seems to work appropriately
  • I suspect as long as other pdb debuggers have the same API as pdb (i.e. if they can be used by the PYTHONBREAKPOINT environment variable) then it should work
  • It's really unclear to me whether changing such deep attributes will have unexpected effects, so not sure how much I recommend this approach

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 esg