I'm on a quest for making a good debug_print()
method for printing out debug information in some of my private projects. When testing I aliased my debug_print()
imported from a module to debug()
print, and some of the intrinsic of my code stopped working.
This led to a question on Stack Overflow, which with a little nudge in the right direction from SuperBiasedMan, led to the following code which I would like for you to review. First the code from utilities.py
:
import inspect
import re
DEBUG_FLAG = True
def _log_args(*args):
"""Uses reflection to returning passing argument code with values."""
prev_frame = inspect.currentframe().f_back
func_name = prev_frame.f_code.co_name
code_context = inspect.getframeinfo(prev_frame.f_back).code_context[0].strip()
# Do some magic, which does work _unless_ func_name is aliased :-)
print('code context : {}'.format(code_context))
print('func_name : {}'.format(func_name))
## Main focus area for review starts here
# Get globals from the calling frame
globals_copy = prev_frame.f_back.f_globals
# Extract legal tokens from code_context
tokens = re.compile('[_a-zA-Z][a-zA-Z_0-9]*').findall(code_context)
for token in tokens:
# print( ' Checking token : {}'.format(token))
# Check if token is found as an object in globals()
code_object = globals_copy.get(token, None)
if not code_object:
continue
# Check if code_object is one of my userdefined functions
if inspect.isfunction(code_object):
code_func_name = getattr(code_object, '__name__', None)
else:
continue
# Check if expanded token is actually an alias (or equal) to func_name
if code_func_name == func_name:
func_name = token
break
else:
# For-loop went through all tokens, and didn't find anything
func_name = None
if func_name:
print('Calling function : {}'.format(func_name))
else:
print('Didn\'t find a calling function?!')
## Main focus area for review ends here
return ', '.join(str(arg) for arg in args)
def format_args(*args):
"""Returns string with name of arguments with values."""
return _log_args(args)
def debug_print(*args):
"""Prints name of arguments with values."""
if DEBUG_FLAG:
print _log_args(args)
And then some test code from another file:
from utilities import debug_print, format_args, debug_print as debug, format_args as fargs
def main():
a, b = "text", (12, 13)
print "== Unaliased =="
test_text = format_args(a, b)
print test_text # Returns
debug_print(a, b)
print "\n== Aliased =="
test_text = fargs(a, b)
print test_text # Returns
debug(a, b)
if __name__ == '__main__':
main()
The current output reflects that this is a work in progress, and the main focus for this review is commented upon in the code.
== Unaliased ==
code context : test_text = format_args(a, b)
func_name : format_args
Calling function : format_args
('text', (12, 13))
code context : debug_print(a, b)
func_name : debug_print
Calling function : debug_print
('text', (12, 13))
== Aliased ==
code context : test_text = fargs(a, b)
func_name : format_args
Calling function : fargs
('text', (12, 13))
code context : debug(a, b)
func_name : debug_print
Calling function : debug
('text', (12, 13)
Addendum: Clarifying context
Note that this addendum to my question is just to help understand the context in which these functions are to be called. The context is that I would want debug output during developing and initial testing of my projects, and I want to lazily do stuff like:
from random import randint
from operator import add, sub
first, second = randint(1, 20), randint(1, 20)
action = add if first > 10 else sub
debug_print(first, action, second)
print('Result is: {}'.format(action(first, second)))
Which could/should result in the output:
first: 6, action: <built-in function sub>, second: 8
Result is: -2
The format_args()
version is for use in logger.debug(format_args(...))
. So "name of argument with value" with an argument of a
will mean "a: 6" if it had the value of 6
.