你会如何在python中编写一个@debuggable装饰器?

时间:2009-05-14 11:26:38

标签: python decorator

调试时,我喜欢打印出函数的所有输入和输出(我知道我需要一个更好的IDE,但是幽默我,这可以用于错误报告)。所以,我最好还是喜欢:

@debuggable
def myfunc(argA,argB,argC):
    return argB+1

并使用全局变量来打开或关闭调试。不,我猜你也不喜欢全球化。

我能想到的最好的是:

DEBUG = True

def debuggable(func):
    if DEBUG:
        def decorated(*args):
            print "Entering ",func.func_name
            print "    args ",args
            ret = func(*args)
            print ret
            return ret
        return decorated
    else:
        return func

@debuggable
def myfunc(this,that):
    return this+that

跑步:

>>> myfunc(1,3)
Entering  myfunc
   args  (1, 3)
4

我该如何改进?

6 个答案:

答案 0 :(得分:22)

使用调试器。认真。装饰你想要跟踪的每个功能是一个坏主意。

Python has a debugger included,因此您不需要一个好的IDE。

如果您不想使用调试器,可以使用trace function

import sys

@sys.settrace
def trace_debug(frame, event, arg):
    if event == 'call':
        print ("calling %r on line %d, vars: %r" % 
                (frame.f_code.co_name, 
                 frame.f_lineno,
                 frame.f_locals))
        return trace_debug
    elif event == "return":
        print "returning", arg

def fun1(a, b):
    return a + b

print fun1(1, 2)

打印:

calling 'fun1' on line 14, vars: {'a': 1, 'b': 2}
returning 3
3

更容易使用Winpdb

它是一个平台独立的图形GPL Python调试器,支持通过网络进行远程调试,多线程,命名空间修改,嵌入式调试,加密通信,速度比pdb快20倍。< / p>

特点:

  • GPL许可证。 Winpdb是自由软件。
  • 与CPython 2.3或更高版本兼容。
  • 与wxPython 2.6或更高版本兼容。
  • 独立于平台,并在Ubuntu Gutsy和Windows XP上进行测试。
  • 用户界面:rpdb2是基于控制台的,而winpdb需要wxPython 2.6或更高版本。

Screenshot
(来源:winpdb.org

答案 1 :(得分:10)

我认为你所追求的并不是真正的调试装饰者,而是更多的日志装饰者。

使用Python's logging module可能有意义,因此您可以对日志本身进​​行更细粒度的控制。例如,您可以输出到文件以便以后分析输出。

装饰者可能看起来更像:


import logging

logger = logging.getLogger('TraceLog')
# TODO configure logger to write to file/stdout etc, it's level etc


def logthis(level):
    def _decorator(fn):
        def _decorated(*arg,**kwargs):
            logger.log(level, "calling '%s'(%r,%r)", fn.func_name, arg, kwargs)
            ret=fn(*arg,**kwargs)
            logger.log(level, "called '%s'(%r,%r) got return value: %r", fn.func_name, arg, kwargs, ret)
            return ret
        return _decorated
    return _decorator

@logthis(logging.INFO)
def myfunc(this,that):
    return this+that

然后,如果您将记录器配置为输出到stderr,您会看到:


>>> logger.setLevel(logging.INFO)
>>> handler=logging.StreamHandler()
>>> logger.addHandler(handler)
>>> myfunc(1,2)
calling 'myfunc'((1, 2),{})
called 'myfunc'((1, 2),{}) got return value: 3

答案 2 :(得分:7)

我同意使用调试器的nosklo比编写自己的调试器要好得多。我将对您的代码发布一项改进。但我仍然认为你应该遵循nosklo的建议。

使用装饰器类使调试器更整洁:

class Debugger(object):
    enabled = False
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        if self.enabled:
            print 'Entering', self.func.func_name 
            print '    args:', args, kwargs
        return self.func(*args, **kwargs)

Debugger.enabled = True

@Debugger
def myfunc(a, b, c, d):
    pass

答案 3 :(得分:1)

这是我的调试装饰器。

import sys
import time

level = 0

def debug(f):
    def decorated(*args, **kwargs):
        global level
        sys.stderr.write("[debug] %s%s(%s)\n"
            % (' ' * level,
                f.__name__, ", ".join(
                    [str(a) for a in args]
                    + ["%s=%s" % (k, v) for (k, v) in kwargs.items()])))
        level += 1
        t0 = time.time()
        res = f(*args, **kwargs)
        t1 = time.time()
        level -= 1
        sys.stderr.write("[debug] %s= %r (%.9f s)\n"
            % (' ' * level, res, t1 - t0))
        return res
    return decorated

这是对 Phil 的改进,因为:

  • 它使用标准错误,因此在标准输出上保留有意义的输出,
  • 它提高了递归调用的可见性
  • 它测量被调试函数的持续时间(不是请求,但也不是很大的障碍)

我使用 f.__name__ 而不是 f.func_name,因为前者在 python 3.8 中不再起作用。

我从约翰蒙哥马利那里得到了提示,不要忘记治疗 kwargs。

我没有保留 DEBUG 全局变量,不是因为它的性质,而是因为我认为在我想要调试/清理它的地方放置/删除带有 @debug 的行更简单。我不会提交它们,因为我将准时手动调试与日志记录区分开来。

小例子

@debug
def f(a, b):
    return a + b

@debug
def g(a, b, c):
    return f(a, b) * c

class C:
    @debug
    def m1(self, a, b):
        return a / b

    @classmethod
    @debug
    def m2(c, a, b):
        return b - a

    @staticmethod
    @debug
    def m3(a, b):
        return a * b

if __name__ == '__main__':
    f(1, b=2)
    g(1, 2, 3)
    C().m1(3, 4)
    C.m2(5, 6)
    C.m3(7, 8)

输出

$./test.py >/dev/null
[debug] f(1, b=2)
[debug] = 3 (0.000001431 s)
[debug] g(1, 2, 3)
[debug]  f(1, 2)
[debug]  = 3 (0.000000477 s)
[debug] = 9 (0.000024557 s)
[debug] m1(<__main__.C object at 0x7f10a6465d90>, 3, 4)
[debug] = 0.75 (0.000000715 s)
[debug] m2(<class '__main__.C'>, 5, 6)
[debug] = 1 (0.000000477 s)
[debug] m3(7, 8)
[debug] = 56 (0.000000477 s)

我理解真正的调试器是一个应该知道的工具,但应该考虑到在编辑器中添加一个小装饰器的简单性。与任何工具一样,正确使用取决于上下文。

答案 4 :(得分:0)

我是nosklo说的第二个。

另一件需要注意的事情是你的功能有点危险:

b = myfunc(1,3)

在这种情况下,“b”是None,因为修饰函数不会返回任何内容。

答案 5 :(得分:0)

您可以用作调试器的装饰器; (@debug)

def debug(func):
    import functools

    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]  # 1
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)  # 3
        print(f"Call {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} return {value!r}")  # 4
        return value

    return wrapper_debug