TL DR : bdb.Bdb
so that it adds the name of the decorator module to the list of skipped code. This works with both pdb
and ipdb
, possibly with many others.
From my own experiments with pdb.Pdb
(the class that actually does debugging in the case of pdb and ipdb), it seems like this is perfectly feasible without changing either the code of the function you want to debug or the decorator.
Python debuggers have features that let you skip some predefined code. In the end, the debugger must skip its own code in order to be useful.
In fact, the base class for Python debuggers has something called a " skip
argument". This is the __init__()
argument, which indicates that the debugger should ignore.
From the Python documentation :
The skip argument, if specified, must be iterable from glob-style module name templates. The debugger will not enter frames created in a module that matches one of these templates. Whether a frame is considered a source in a particular module is determined by its name in the frame globals.
The problem is that this is indicated when calling set_trace()
, after which we have already landed in the decorator frame, for a break. Thus, there is no function that would allow this argument to be added at runtime.
Fortunately, modifying existing code at runtime is easy in Python, and there are hacks that we can use to add a decorator module name with every Bdb.__init__()
. We can βdecorateβ the Bdb
class Bdb
that our module is added to the skip list whenever someone creates a Bdb
object.
So, here is an example of just that. Please excuse the weird signature and use of Bdb.__init__()
instead of super()
- to be compatible with pdb
we have to do it like this:
# magic_decorator.py import bdb old_bdb = bdb.Bdb class DontDebugMeBdb(bdb.Bdb): @classmethod def __init__(cls, *args, **kwargs): if 'skip' not in kwargs or kwargs['skip'] is None: kwargs['skip'] = [] kwargs['skip'].append(__name__) old_bdb.__init__(*args, **kwargs) @staticmethod def reset(*args, **kwargs): old_bdb.reset(*args, **kwargs) bdb.Bdb = DontDebugMeBdb def dont_debug_decorator(func): print("Decorating {}".format(func)) def decorated(): """IF YOU SEE THIS IN THE DEBUGER - YOU LOST""" print("I'm decorated") return func() return decorated
# buged.py from magic_decorator import dont_debug_decorator @dont_debug_decorator def debug_me(): print("DEBUG ME")
The output of ipdb.runcall
in Ipython:
In [1]: import buged, ipdb Decorating <function debug_me at 0x7f0edf80f9b0> In [2]: ipdb.runcall(buged.debug_me) I'm decorated --Call-- > /home/mrmino/treewrite/buged.py(4)debug_me() 3 ----> 4 @dont_debug_decorator 5 def debug_me(): ipdb>