To help me debug some of the code I write, I want to make a function decorator that prints off the name of a variable and its value as each variable is created or modified, essentially giving me a "play-by-play" view of what happens when I call the function.
The approach I had been using up until this point is simply adding a line like print(foo)
wherever I wanted to see what was happening, but this is extremely time-consuming and makes my code messy to look at (possibly the epitome of un-Pythonicness).
Effectively what I want to do is have this:
@show_guts
def foo(a,b):
biz = str(a)
baz = str(b)
return biz + baz
foo("banana","phone")
print something like this in the IDE:
biz = "banana"
baz = "phone"
bananaphone
My question is what @show_guts
would look like. I know it's possible to print only the values of a
and b
with a decorator like
def print_args(function):
def wrapper(*args,**kwargs):
print("Arguments:",args)
print("Keyword Arguments:",kwargs)
return function(*args,**kwargs)
return wrapper
which gives me
Arguments: ('banana', 'phone')
Keyword Arguments: {}
'bananaphone'
but I'm totally stumped as to how to print the local variable names as well as their values. Not to mention do it in a "neat" way.
To get the type of a variable in Python, you can use the built-in type() function. In Python, everything is an object. So, when you use the type() function to print the type of the value stored in a variable to the console, it returns the class type of the object.
Locals() is a built-in function that returns a list of all local variables in that particular scope. And globals() does the same with the global variables. Create a list of all global variables using globals( ) function, to store the built-in global variables. Declare a function.
You first include the character f before the opening and closing quotation marks, inside the print() function. To print a variable with a string in one line, you again include the character f in the same place – right before the quotation marks.
Using printf function, we can print the value of a variable. In order to print the variable value, we must instruct the printf function the following details, 1. specify the format of variable.
You cannot do this without enabling tracing; this will hurt performance. Function locals are constructed when the function is called, and cleaned up when it returns, so there is no other way to access those locals from a decorator.
You can insert a trace function using sys.settrace()
, then respond to the events the Python interpreter sends that function. What we want to do is trace just the decorated function, and record the locals when the function returns:
import sys
import threading
def show_guts(f):
sentinel = object()
gutsdata = threading.local()
gutsdata.captured_locals = None
gutsdata.tracing = False
def trace_locals(frame, event, arg):
if event.startswith('c_'): # C code traces, no new hook
return
if event == 'call': # start tracing only the first call
if gutsdata.tracing:
return None
gutsdata.tracing = True
return trace_locals
if event == 'line': # continue tracing
return trace_locals
# event is either exception or return, capture locals, end tracing
gutsdata.captured_locals = frame.f_locals.copy()
return None
def wrapper(*args, **kw):
# preserve existing tracer, start our trace
old_trace = sys.gettrace()
sys.settrace(trace_locals)
retval = sentinel
try:
retval = f(*args, **kw)
finally:
# reinstate existing tracer, report, clean up
sys.settrace(old_trace)
for key, val in gutsdata.captured_locals.items():
print '{}: {!r}'.format(key, val)
if retval is not sentinel:
print 'Returned: {!r}'.format(retval)
gutsdata.captured_locals = None
gutsdata.tracing = False
return retval
return wrapper
Demonstration:
>>> @show_guts
... def foo(a,b):
... biz = str(a)
... baz = str(b)
... return biz + baz
...
>>> result = foo("banana","phone")
a: 'banana'
biz: 'banana'
b: 'phone'
baz: 'phone'
Returned: 'bananaphone'
import re
import inspect
assignment_regex = re.compile(r'(\s*)([\w\d_]+)\s*=\s*[\w\d+]')
def show_guts(fn):
source = inspect.getsource(fn)
lines = []
for line in source.split('\n'):
if 'show_guts' in line:
continue
lines.append(line)
if 'def' in line:
# kwargs will match the regex
continue
search = assignment_regex.search(line)
try:
groups = search.groups()
leading_whitespace = groups[0]
variable_name = groups[1]
lines.append(leading_whitespace + 'print "Assigning {0} =", {0}'.format(variable_name))
except AttributeError: # no match
pass
new_source = '\n'.join(lines)
namespace = {}
exec new_source in namespace
fn = namespace[fn.__name__]
def wrapped(*args, **kwargs):
arg_string = ', '.join(map(str, args))
kwarg_string = ', '.join(key + '=' + str(value) for key, value in kwargs.iteritems())
print "Calling", fn.__name__ + '(' + ', '.join((arg_string, kwarg_string)) + ')'
return fn(*args, **kwargs)
return wrapped
Basically, this is automatically doing what you were doing. It gets the source for the function passed in, loop over each line in the source and for each assignment statement, create a new print statement and append it to the source body. The new source is compiled and the function is replaced with the newly compiled function. Then to get the *args
and **kwargs
, I create the normal decorator wrapper function and throw in some nice print statements. That part could probably be a little better with some help from the inspect
module, but whatevs.
# This...
@show_guts
def complicated(a, b, keyword=6):
bar = str(a)
baz = str(b)
if a == b:
if keyword != 6:
keyword = a
else:
keyword = b
return bar + baz
# becomes this
def complicated(a, b, keyword=6):
bar = str(a)
print "Assigning bar =", bar
baz = str(b)
print "Assigning baz =", baz
if a == b:
if keyword != 6:
keyword = a
print "Assigning keyword =", keyword
else:
keyword = b
print "Assigning keyword =", keyword
return bar + baz
@show_guts
def foo(a, b):
bar = str(a)
baz = str(b)
return bar + baz
@show_guts
def complicated(a, b, keyword=6):
bar = str(a)
baz = str(b)
if a == b:
if keyword != 6:
keyword = a
else:
keyword = b
return bar + baz
foo(1, 2)
complicated(3, 4)
complicated(3, 3)
complicated(3, 3, keyword=123)
Calling foo(1, 2, )
Assigning bar = 1
Assigning baz = 2
Calling complicated(3, 4, )
Assigning bar = 3
Assigning baz = 4
Assigning keyword = 4
Calling complicated(3, 3, )
Assigning bar = 3
Assigning baz = 3
Calling complicated(3, 3, keyword=123)
Assigning bar = 3
Assigning baz = 3
Assigning keyword = 3
There are probably some corner cases that I'm missing with the regex, but this will get you close.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With