Is there any way to enter decorated functions by skipping decorator code

I have a module that decorates some key functions with custom decorators.

Debugging these functions with pdb is often a little painful, because every time I enter a designed function, I first need to go through the decorator code itself.

Of course, I could just turn off the debugger inside the function that interests me, but as key functions they are called many times from many places, so I usually prefer to start debugging outside the function.

I tried to illustrate it with code, but I don't know if this helps:

def i_dont_care_about_this(fn): @functiontools.wraps(fn) def wrapper(*args, **kwargs): return fn(*args, **kwargs) return wrapper @i_dont_care_about_this def i_only_care_about_this(): # no use to set pdb here def i_am_here(): import pdb; pdb.set_trace() i_only_care_about_this() 

So, is there a way to go to i_only_care_about_this from i_am_here without going through i_dont_care_about_this ?

Essentially, I want to skip all the decorator code when using s in ( s ) tep in a given decorated function.

+7
source share
4 answers

If the decorator is intended solely for logging or other non-functional behavior, then make it no-op for debugging - paste this code immediately after determining i_dont_care_about_this :

 DEBUG = False # uncomment this line when pdb'ing # DEBUG = True if DEBUG: i_dont_care_about_this = lambda fn : fn 

But if it contains the actual active code, you will have to do the work using pdb methods, such as conditionally calling pdb.set_trace inside the code inside the decorator:

 BREAK_FLAG = False ... # (inside your function you want to debug) if BREAK_FLAG: import pdb; pdb.set_trace() ... # at your critical calling point BREAK_FLAG = True 
+7
source

I do not think you can do this. This would change the meaning of the step as something completely different.

However, there is a way to achieve something similar to what you want. Set a breakpoint in your decorated function and one before calling the decorated function. Now disable the breakpoint inside the function.

Now, when you run the code, it will break only when you reach the specific call that you care about. Once this break occurs, re-enable the breakpoint in the function and continue execution. This will execute all the decorated code and break on the first line of the decorated function.

+3
source

With the following:

 def my_decorator(fn): def wrapper(*args, **kwargs): return fn(*args, **kwargs) return wrapper @my_decorator def my_func(): ... 

I call pdb using import pdb; pdb.run('my_func()') import pdb; pdb.run('my_func()') import pdb; pdb.run('my_func()') import pdb; pdb.run('my_func()') which introduces pdb here:

 > <string>(1)<module>() 
  1. step to enter the call stack - now we look at the first line of the decorator function definition:

      def my_decorator(fn): -> def wrapper(*args, **kwargs): return fn(*args, **kwargs) return wrapper 
  2. next until pdb is pdb (pointing to) the line into which we return original function (it can be next or a multiple function - it depends only on your code):

      def my_decorator(fn): def wrapper(*args, **kwargs): -> return fn(*args, **kwargs) return wrapper 
  3. step to the original function and voila! we are now at the point where we can next our original function.

     -> @my_decorator def my_funct(): ... 
0
source

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> 
0
source

Source: https://habr.com/ru/post/914713/


All Articles