有没有办法进入装饰函数,跳过装饰器代码

时间:2012-05-02 11:44:06

标签: python decorator pdb

我有一个模块用自定义装饰器装饰一些关键功能。

使用pdb调试这些函数通常会有点痛苦,因为每次我进入一个装饰函数时,我首先必须逐步完成装饰器代码。

我当然可以将调试器设置为在我感兴趣的函数内断开,但作为关键函数,它们在很多地方被调用很多次,所以我通常更喜欢在函数外部开始调试。

我试图用代码说明它,但我不知道这是否有帮助:

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()

那么,有没有办法让我从i_only_care_about_this进入i_am_here而无需经过i_dont_care_about_this

基本上我想在使用 s 到( s )tep到给定的装饰函数时跳过所有装饰器代码。

4 个答案:

答案 0 :(得分:6)

如果装饰器纯粹用于记录或其他非功能行为,那么使其成为调试的无操作 - 在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

但如果它包含实际的活动代码,那么你将不得不使用pdb方法进行工作,例如在装饰器内的代码内对pdb.set_trace进行条件化调用:

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

答案 1 :(得分:3)

我认为你不能那样做。它会将步骤的含义改变为非常不同的东西。

然而,有一种方法可以实现类似于你想要的东西。在装饰函数中设置一个断点,并在调用装饰函数之前设置一个断点。现在,禁用函数内的断点。

现在,当您运行代码时,它只会在您到达您关注的特定调用时中断。一旦发生中断,重新启用函数中的断点并继续执行。这将执行所有修饰的代码并在装饰函数的第一行中断。

答案 2 :(得分:1)

TL; DR 修改bdb.Bdb,以便将装饰器的模块名称添加到跳过的代码列表中。 pdbipdb都可以使用,可能还可以使用其他许多功能。 底部的示例

根据我自己对pdb.Pdb(在pdb和ipdb情况下实际上进行调试的类)的实验,似乎完全可行,而无需修改要调试的函数的代码或装饰器。

Python调试器具有一些设施,可以跳过一些预定义的代码。毕竟,调试器必须跳过自己的代码才能使用。

实际上,Python调试器的基类有一个称为“ skip参数”的东西。它是__init__()的参数,它指定调试器应忽略的内容。

来自Python Documentation

  

skip参数(如果提供)必须是glob样式的模块名称模式的可迭代项。调试器将不会进入源自与这些模式之一匹配的模块的框架。帧是否被认为起源于某个模块由帧全局变量中的名称确定。

此问题在于,它是在调用set_trace()时指定的,此后我们已经暂停进入装饰器的框架。因此,那里没有可以让我们在运行时添加到该参数的功能。

幸运的是,在Python中,在运行时修改现有代码很容易,并且有一些技巧可以在调用Bdb.__init__()时添加装饰器的模块名称。我们可以“装饰” Bdb类,以便在有人创建Bdb对象时将我们的模块添加到跳过列表中。

因此,这里就是一个例子。请原谅Bdb.__init__()而不是super()的怪异签名和用法-为了与pdb兼容,我们必须这样做:

# 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")

Ipython中ipdb.runcall的输出:

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>                                                  

答案 3 :(得分:0)

具有以下内容:

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

@my_decorator
def my_func():
    ...

我用pdb调用import pdb; pdb.run('my_func()'),在这里输入pdb

> <string>(1)<module>()
  1. step进入调用堆栈–我们现在查看装饰器函数定义的第一行:

       def my_decorator(fn):
    ->     def wrapper(*args, **kwargs):
               return fn(*args, **kwargs)
           return wrapper
    
  2. next,直到pdb指向(指向)我们原始功能return所在的行(可能是一个next或多个–仅取决于在您的代码上):

       def my_decorator(fn):
           def wrapper(*args, **kwargs):
    ->         return fn(*args, **kwargs)
           return wrapper
    
  3. step进入原始功能,瞧!现在,我们可以通过我们的原始功能next了。

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