This is a follow-up to this question.
I've refactored my previous debugging decorator, and added a couple new features, and changed a few things. Here's a complete list of things that have changed:
- There is only one decorator,
Debug, and it now supports functions, and class methods. - Each debug message is prefixed with
[debug]to help distinguish it from normal output. - The output now tells you what it's outputting, rather than just outputting unreadable data.
- The decorator will now output local variables names, along with argument and keyword argument names as well.
I'm wondering the following:
- Is there a way to get the values of local variables in the function, or is that just not possible?
- Is there a shorter way to get the names of local variables than
function.__code__.co_varnames? - Is it a good idea to create an empty string, and then add to, and re-assign it to build an output string?
- Is this Python 3, and Python 2.7 compatible?
- How's my documentation?
- Is this code "pythonic"?
debug.py
from pprint import pformat
from inspect import getargspec
class Debug(object):
"""Decorator for debugging functions.
This decorator is used to debug a function, or
a class method. If this is applied to a normal
function, it will print out the arguments of
Keyword arguments:
debug -- Whether or not you want to output debug info. Generally, a global DEBUG variable is passed in here.
"""
def __init__(self, debug=True):
self.debug = debug
def __format_debug_string(self, function, *args, **kwargs):
"""Return a formatted debug string.
This is a small private helper function that will
return a string value with certain debug information.
Keyword arguments:
function -- The function to debug.
*args -- The normal arguments of the function.
**kwargs -- The keyword arguments of the function.
"""
debug_string = ""
debug_string += "[debug] {}\n".format(pformat(function))
debug_string += "[debug] Passed args: {}\n".format(pformat(args))
debug_string += "[debug] Passed kwargs: {}\n".format(pformat(kwargs))
debug_string += "[debug] Locals: {}".format(pformat(function.__code__.co_varnames))
return debug_string
def __call__(self, function):
def wrapper(*args, **kwargs):
if self.debug:
if getargspec(function).args[0] != "self":
print(self.__format_debug_string(function, *args, **kwargs))
else:
print(self.__format_debug_string(function, *args, **kwargs))
print("[debug] Parent attributes: {}".format(pformat(args[0].__dict__)))
return function(*args, **kwargs)
return wrapper
Here are a few small, albeit unreadable tests, but it's good enough to get the point across:
from debug import Debug
@Debug(debug=True)
def a(a, b):
d = 10
return a * b
print(a(10, 10))
class B(object):
def __init__(self, a, b):
self.a = a
self.b = b
@Debug(debug=True)
def e(self, c):
return self.a * self.b * c
c = B(10, 10)
print(c.e(10))
Here's the output of these tests:
[debug] <function a at 0x1bf9d38> [debug] Passed args: (10, 10) [debug] Passed kwargs: {} [debug] Locals: ('a', 'b', 'd') 100 [debug] <function B.e at 0x1944ce8> [debug] Passed args: (<B object at 0x1bfc838>, 10) [debug] Passed kwargs: {} [debug] Locals: ('self', 'c') [debug] Parent attributes: {'a': 10, 'b': 10} 1000