如何在Python中编写@debuggable装饰器?

13 投票
6 回答
8435 浏览
提问于 2025-04-15 11:35

在调试的时候,我喜欢把一个函数的所有输入和输出都打印出来(我知道我需要一个更好的开发工具,但请理解,这可以用来报告错误)。所以,我理想的情况是:

@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 个回答

11

我同意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
13

我觉得你想要的其实不是一个调试装饰器,而是一个记录日志的装饰器。

使用Python的日志模块可能会更合适,这样你可以更细致地控制日志的记录。例如,你可以把日志输出到一个文件里,方便以后分析。

这个装饰器可能看起来像这样:


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

25

使用调试工具。真的,给每个你想跟踪的函数加装饰器并不是个好主意。

Python 自带了一个调试工具,你不需要一个好的开发环境

如果你不想用调试工具,可以使用 trace 函数

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 倍。

特点:

  • GPL 许可证。Winpdb 是免费软件。
  • 兼容 CPython 2.3 或更高版本。
  • 兼容 wxPython 2.6 或更高版本。
  • 平台无关,已在 Ubuntu Gutsy 和 Windows XP 上测试。
  • 用户界面:rpdb2 是基于控制台的,而 winpdb 需要 wxPython 2.6 或更高版本。

截图
(来源: winpdb.org)

撰写回答