我有一个模块用自定义装饰器装饰一些关键功能。
使用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到给定的装饰函数时跳过所有装饰器代码。
答案 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
,以便将装饰器的模块名称添加到跳过的代码列表中。 pdb
和ipdb
都可以使用,可能还可以使用其他许多功能。 底部的示例。
根据我自己对pdb.Pdb
(在pdb和ipdb情况下实际上进行调试的类)的实验,似乎完全可行,而无需修改要调试的函数的代码或装饰器。
Python调试器具有一些设施,可以跳过一些预定义的代码。毕竟,调试器必须跳过自己的代码才能使用。
实际上,Python调试器的基类有一个称为“ skip
参数”的东西。它是__init__()
的参数,它指定调试器应忽略的内容。
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>()
step
进入调用堆栈–我们现在查看装饰器函数定义的第一行:
def my_decorator(fn):
-> def wrapper(*args, **kwargs):
return fn(*args, **kwargs)
return wrapper
next
,直到pdb
指向(指向)我们原始功能return
所在的行(可能是一个next
或多个–仅取决于在您的代码上):
def my_decorator(fn):
def wrapper(*args, **kwargs):
-> return fn(*args, **kwargs)
return wrapper
step
进入原始功能,瞧!现在,我们可以通过我们的原始功能next
了。
-> @my_decorator
def my_funct():
...