Here is my debug decorator.
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
This is an improvement over Phil’s because :
- it uses stderr, hence preserving meaningful output on stdout,
- it improves visibility on recursive calls
- it measures the duration of the debugged function (not requested, but not a big hindrance either)
I use f.__name__
instead of f.func_name
, because the former doesn’t work anymore in python 3.8.
I took the hint from John Montgomery to not forget to treat kwargs.
I did not keep the DEBUG global variable, not because of its nature, but because I consider it simpler to put/remove a line with @debug
where I want it debugged/cleaned. I won’t commit them, as I differentiate punctual manual debug to logging.
Small example
@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)
Output
$./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)
I understand that a real debugger is a tool that should be known, but the simplicity of adding a small decorator in the editor should be taken in account. As with any tool, proper usage is defined by context.