【问题标题】:How would you write a @debuggable decorator in python?您将如何在 python 中编写@debuggable 装饰器?
【发布时间】:2010-10-26 03:45:24
【问题描述】:

在调试时,我喜欢打印出一个函数的所有输入和输出(我知道我需要一个更好的 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

我该如何改进?

【问题讨论】:

  • Word Aligned 上有一篇关于跟踪装饰器主题的相当长的博客文章。

标签: python decorator


【解决方案1】:

使用调试器。严重地。装饰您想要跟踪的每个功能是一个坏主意。

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

特点:

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


(来源:winpdb.org

【讨论】:

  • 虽然这是很好的信息,但它并不是问题的真正答案。
【解决方案2】:

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

使用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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 不错。我不知道类可以用作装饰器,但我想应该很明显这是可能的。 ;p
    【解决方案4】:

    这是我的调试装饰器。

    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 中不再适用。

    我从 John Montgomery 那里得到了不要忘记治疗 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)
    

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

    【讨论】:

      【解决方案5】:

      您可以用作装饰器进行调试; (@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
      

      【讨论】:

        【解决方案6】:

        我赞同 nosklo 所说的。

        另外需要注意的是你的函数有点危险:

        b = myfunc(1,3)
        

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

        【讨论】:

          猜你喜欢
          • 2011-12-26
          • 2012-10-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-01-28
          相关资源
          最近更新 更多