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
1 Answer 1
You can improve the following:
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__)))
If the code is executed no matter the statement, and it always goes first, move it above the condition: (and don't forget to reverse the condition)
print(self.__format_debug_string(function, *args, **kwargs))
if getargspec(function).args[0] == "self":
print("[debug] Parent attributes: {}".format(pformat(args[0].__dict__)))
As for this:
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
You can remove the = ""
entirely:
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))
return debug_string + "[debug] Locals: {}".format(pformat(function.__code__.co_varnames))
It may not look as visually stimulating, but, it's not as redundant.
Is it a good idea to create an empty string, and then add to, and re-assign it to build an output string?
If you were directly printing these then it would be a bad idea, but in this case, not really. However, I suppose you could move them to an object, or an array and return the result of a join
function.
You could even return it as an array, and print each [debug]
result. Which would remove the need for the \n
s at the end, and DRY up the [debug]
at the beginning of the strings (put it in the loop, not altogether)
You've got a few too long lines, by PEP8 standard:
debug -- Whether or not you want to output debug info. Generally, a global DEBUG variable is passed in here. debug_string += "[debug] Locals: {}".format(pformat(function.__code__.co_varnames)) print(self.__format_debug_string(function, *args, **kwargs)) print("[debug] Parent attributes: {}".format(pformat(args[0].__dict__)))
As for your documentation:
function, it will print out the arguments of Keyword arguments:
I'm a bit confused by that, grammatically.
__call__
is a more complex function (in my mind, at least) than __format_debug_string
, but it has no documentation.
Is this Python 3, and Python 2.7 compatible?
It ran fine when I tested it in Python 2.7.9 and 3.1.1
-
\$\begingroup\$ It doesn't have the ending underscores because it would imply that I'm overloading a "magic method". \$\endgroup\$Ethan Bierlein– Ethan Bierlein2015年09月10日 16:01:15 +00:00Commented Sep 10, 2015 at 16:01