15

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

使用 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_thisi_am_here经过而进入i_dont_care_about_this

本质上,我想在使用s to ( s ) 进入给定的装饰函数时跳过所有装饰器代码。

4

5 回答 5

8

如果装饰器纯粹用于日志记录或其他非功能性行为,则使其成为调试无操作 - 在定义之后立即插入此代码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
于 2012-05-02T13:02:49.200 回答
4

我不认为你能做到这一点。它会将 step 的含义改变为非常不同的东西。

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

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

于 2012-05-02T11:58:53.517 回答
2

TL;DR修改bdb.Bdb,以便将装饰器的模块名称添加到跳过的代码列表中。这适用于两者pdbipdb可能还有许多其他人。底部的例子

从我自己的实验pdb.Pdb(在 pdb 和 ipdb 的情况下实际进行调试的类)看来,它似乎是完全可行的,既不修改要调试的函数的代码也不修改装饰器。

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

事实上,python 调试器的基类有一个叫做“skip参数”的东西。它是 it's 的一个参数,它__init__()指定调试器应该忽略的内容。

来自Python 文档

如果给定,skip 参数必须是 glob 样式的模块名称模式的可迭代。调试器不会单步执行源自与这些模式之一匹配的模块的帧。框架是否被认为源自某个模块由框架全局变量中的 __name__ 确定。

这样做的问题是它是在对 的调用中指定的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")

ipdb.runcall在 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>                                                  
于 2018-12-08T17:52:37.583 回答
0

不完全是问题的答案,但对于新手来说,如果有人在VSCode中调试装饰函数以跳过装饰器并进入函数,请执行以下操作:

  1. 在您装饰的函数内放置一个断点(函数体)
  2. 调用该函数并进行统计调试
  3. 而不是单击step overstep into单击continue,您最终会进入一个函数。
  4. 照常继续调试

例如:

@some_decorator
def say_hello(name):
    x = f"Hello {name}"
    return x


hello = say_hello(name="John")

在函数内部放置一个断点hello和第二个断点。x

于 2021-09-20T18:01:21.127 回答
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()')which 进入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():
           ...
    
于 2018-08-21T22:36:14.947 回答