I'm writing wrappers for the Python print function, but my question is more general - having wrapped a function, what's the proper way to un-wrap it?
This works, but I have two concerns about it:
class Iprint():
def __init__(self, tab=4, level=0):
''' Indented printer class.
tab controls number of spaces per indentation level (equiv. to tabstops)
level is the indentation level (0=none)'''
global print
self.tab = tab
self.level = level
self.old_print = print
print = self.print
def print(self, *args, end="\n", **kwargs):
indent = self.tab * self.level
self.old_print(" "*indent, end="", **kwargs)
self.old_print(*args, end=end, **kwargs)
indent = Iprint()
indent.level = 3
print("this should be indented")
print = indent.old_print
print("this shouldn't be indented")
My two concerns:
What happens if there's a second instantiation of the Iprint() class? This seems awkward and maybe something I ought to prevent - but how?
The 2nd to last line print = indent.old_print "unwraps" the print function, returning it to it's original function. This seems awkward too - what if it's forgotten?
I could do it in an __exit__ method but that would restrict the use of this to a with block - I think. Is there a better way?
What's the Pythonic way to do this?
(I also should mention that I anticipate having nested wrappers, which I thinks makes doing this properly more important...)
What it seems you are really trying to do here is find a way to override the builtin print function in a "pythonic" way.
While there is a way to do this, I do have a word of caution. One of the rules of "pythonic code" is
Explicit is better than implicit.
Overwriting print is inherently an implicit solution, and it would be more "pythonic" to allow for a custom print function to solve your needs.
However, let's assume we are talking about a use case where the best option available is to override print. For example, lets say you want to indent the output from the help() function.
You could override print directly, but you run the risk of causing unexpected changes you can't see.
For example:
def function_that_prints():
log_file = open("log_file.txt", "a")
print("This should be indented")
print("internally logging something", file = log_file)
log_file.close()
indent = Iprint()
indent.level = 3
function_that_prints() # now this internal log_file.txt has been corrupted
print = indent.old_print
This is bad, since presumably you just meant to change the output that is printed on screen, and not internal places where print may or may not be used.
Instead, you should just override the stdout, not print.
Python now includes a utility to do this called contextlib.redirect_stdout() documented here.
An implementation may look like this:
import io
import sys
import contextlib
class StreamIndenter(io.TextIOBase):
# io.TextIOBase provides some base functions, such as writelines()
def __init__(self, tab = 4, level = 1, newline = "\n", stream = sys.stdout):
"""Printer that adds an indent at the start of each line"""
self.tab = tab
self.level = level
self.stream = stream
self.newline = newline
self.linestart = True
def write(self, buf, *args, **kwargs):
if self.closed:
raise ValueError("write to closed file")
if not buf:
# Quietly ignore printing nothing
# prevents an issue with print(end='')
return
indent = " " * (self.tab * self.level)
if self.linestart:
# The previous line has ended. Indent this one
self.stream.write(indent)
# Memorize if this ends with a newline
if buf.endswith(self.newline):
self.linestart = True
# Don't replace the last newline, as the indent would double
buf = buf[:-len(self.newline)]
self.stream.write(buf.replace(self.newline, self.newline + indent))
self.stream.write(self.newline)
else:
# Does not end on a newline
self.linestart = False
self.stream.write(buf.replace(self.newline, self.newline + indent))
# Pass some calls to internal stream
#property
def writable(self):
return self.stream.writable
#property
def encoding(self):
return self.stream.encoding
#property
def name(self):
return self.stream.name
with contextlib.redirect_stdout(StreamIndenter()) as indent:
indent.level = 2
print("this should be indented")
print("this shouldn't be indented")
Overriding print this way both doesn't corrupt other uses of print and allows for proper handling of more complicated usages.
For example:
with contextlib.redirect_stdout(StreamIndenter()) as indent:
indent.level = 2
print("this should be indented")
indent.level = 3
print("more indented")
indent.level = 2
for c in "hello world\n": print(c, end='')
print()
print("\n", end='')
print(end = '')
print("this shouldn't be indented")
Formats correctly as:
this should be indented
more indented
hello world
this shouldn't be indented
I think I've solved this - at least to my own satisfaction. Here I've called the class T (for test):
class T():
old_print = None
def __init__(self, tab=4, level=0):
''' Indented printer class.
tab controls number of spaces per indentation level (equiv. to tabstops)
level is the indentation level (0=none)'''
T.tab = tab
T.level = level
self.__enter__()
def print(self, *args, end="\n", **kwargs):
indent = T.tab * T.level
T.old_print(" "*indent, end="", **kwargs)
T.old_print(*args, end=end, **kwargs)
def close(self):
if T.old_print is not None:
global print
print = T.old_print
T.old_print = None
def __enter__(self):
if T.old_print is None:
global print
T.old_print = print
print = self.print
def __exit__(self, exception_type, exception_value, exception_traceback):
self.close()
print("this should NOT be indented")
i = T(level=1)
print("level 1")
i2 = T(level=2)
print("level 2")
i.close()
print("this should not be indented")
i3 = T(level=3)
print("level 3")
i2.close()
print("not indented")
with i:
print("i")
print("after i")
with T(level=3):
print("T(level=3)")
print("after T(level=3)")
It silently forces a single (functional) instance of the class, regardless of how many times T() is called, as #MichaelButscher suggested (thanks; that was the most helpful comment by far).
It works cleanly with WITH blocks, and you can manually call the close method if not using WITH blocks.
The output is, as expected:
this should NOT be indented
level 1
level 2
this should not be indented
level 3
not indented
i
after i
T(level=3)
after T(level=3)
Related
I have a python function that runs other functions.
def main():
func1(a,b)
func2(*args,*kwargs)
func3()
Now I want to apply exceptions on main function. If there was an exception in any of the functions inside main, the function should not stop but continue executing next line. In other words, I want the below functionality
def main():
try:
func1()
except:
pass
try:
func2()
except:
pass
try:
func3()
except:
pass
So is there any way to loop through each statement inside main function and apply exceptions on each line.
for line in main_function:
try:
line
except:
pass
I just don't want to write exceptions inside the main function.
Note : How to prevent try catching every possible line in python? this question comes close to solving this problem, but I can't figure out how to loop through lines in a function.
If you have any other way to do this other than looping, that would help too.
What you want is on option that exists in some languages where an exception handler can choose to proceed on next exception. This used to lead to poor code and AFAIK has never been implemented in Python. The rationale behind is that you must explicitely say how you want to process an exception and where you want to continue.
In your case, assuming that you have a function called main that only calls other function and is generated automatically, my advice would be to post process it between its generation and its execution. The inspect module can even allow to do it at run time:
def filter_exc(func):
src = inspect.getsource(func)
lines = src.split('\n')
out = lines[0] + "\n"
for line in lines[1:]:
m = re.match('(\s*)(.*)', line)
lead, text = m.groups()
# ignore comments and empty lines
if not (text.startswith('#') or text.strip() == ""):
out += lead + "try:\n"
out += lead + " " + text + "\n"
out += lead + "except:\n" + lead + " pass\n"
return out
You can then use the evil exec (the input in only the source from your function):
exec(filter_exc(main)) # replaces main with the filtered version
main() # will ignore exceptions
After your comment, you want a more robust solution that can cope with multi line statements and comments. In that case, you need to actually parse the source and modify the parsed tree. ast module to the rescue:
class ExceptFilter(ast.NodeTransformer):
def visit_Expr(self, node):
self.generic_visit(node)
if isinstance(node.value, ast.Call): # filter all function calls
# print(node.value.func.id)
# use a dummy try block
n = ast.parse("""try:
f()
except:
pass""").body[0]
n.body[0] = node # make the try call the real function
return n # and use it
return node # keep other nodes unchanged
With that example code:
def func1():
print('foo')
def func2():
raise Exception("Test")
def func3(x):
print("f3", x)
def main():
func1()
# this is a comment
a = 1
if a == 1: # this is a multi line statement
func2()
func3("bar")
we get:
>>> node = ast.parse(inspect.getsource(main))
>>> exec(compile(ExceptFilter().visit(node), "", mode="exec"))
>>> main()
foo
f3 bar
In that case, the unparsed node(*) write as:
def main():
try:
func1()
except:
pass
a = 1
if (a == 1):
try:
func2()
except:
pass
try:
func3('bar')
except:
pass
Alternatively it is also possible to wrap every top level expression:
>>> node = ast.parse(inspect.getsource(main))
>>> for i in range(len(node.body[0].body)): # process top level expressions
n = ast.parse("""try:
f()
except:
pass""").body[0]
n.body[0] = node.body[0].body[i]
node.body[0].body[i] = n
>>> exec(compile(node, "", mode="exec"))
>>> main()
foo
f3 bar
Here the unparsed tree writes:
def main():
try:
func1()
except:
pass
try:
a = 1
except:
pass
try:
if (a == 1):
func2()
except:
pass
try:
func3('bar')
except:
pass
BEWARE: there is an interesting corner case if you use exec(compile(... in a function. By default exec(code) is exec(code, globals(), locals()). At top level, local and global dictionary is the same dictionary, so the top level function is correctly replaced. But if you do the same in a function, you only create a local function with the same name that can only be called from the function (it will go out of scope when the function will return) as locals()['main'](). So you must either alter the global function by passing explicitely the global dictionary:
exec(compile(ExceptFilter().visit(node), "", mode="exec"), globals(), globals())
or return the modified function without altering the original one:
def myfun():
# print(main)
node = ast.parse(inspect.getsource(main))
exec(compile(ExceptFilter().visit(node), "", mode="exec"))
# print(main, locals()['main'], globals()['main'])
return locals()['main']
>>> m2 = myfun()
>>> m2()
foo
f3 bar
(*) Python 3.6 contains an unparser in Tools/parser, but a simpler to use version exists in pypi
You could use a callback, like this:
def main(list_of_funcs):
for func in list_of_funcs:
try:
func()
except Exception as e:
print(e)
if __name__ == "__main__":
main([func1, func2, func3])
Is it possible to determine the level of indentation of a line in Python while the program is running? I want to be able to organize a log file according to an outline structure of the script that is being run.
In the following example, the 'first message' function would yield 0, 'second message' would be 1, 'third message' 2 and 'fourth, fifth, sixth' message would be 0
logger.debug('First message')
if True:
logger.info('Second message')
if True:
logger.info('Third message')
logger.warning('Fourth message')
logger.error('Fifth message')
logger.critical('Sixth message')
The corresponding log file would look something like this:
First message
Second message
Third message
Fourth message
Fifth message
Sixth message
I was able to determine the indentation level using the inspect.getouterframes() function. This assumes that 4 ' ' characters are used instead of '\t' characters for indentation.
import inspect
def getIndentationLevel():
# get information about the previous stack frame
frame, filename, line_number,function_name, lines, index = inspect.getouterframes(inspect.currentframe())[1]
# count the number of characters in the line
original_length = len(lines[0])
# trim the line of left spaces and record the new length
new_length = len(lines[0].lstrip())
# take the difference between the two values and divide by 4 to get the level number
return int((original_length - new_length) / 4)
First, I retrieve the code context of the caller:
import inspect
context = inspect.getframeinfo(frame.f_back).code_context
This gives me a list of code lines; I ignore all but the first of those lines. Then I use a regular expression to get the whitespace at the start of this line; if you are using tabs, you have to replace them with the appropriate amount of spaces first:
import re
indent = re.compile("^ *")
firstline = context[0]
firstline = firstline.replace("\t", " ")
match = indent.match(firstline)
For testing purposes, I replace the spaces with dots, so I can see what's going on, and construct my prefix:
white = "." * match.span(0)[1] # Change "." to " "!
Now I can use this to modify my message msg I sent to my logger:
do_some_logging(white + msg)
It would be nice if I could simply wrap an existing logger with a decorator that turns this logger into a whitespace-aware logger. Suppose I have, for testing purposes, a really primitive logger that simply prints messages to stdout:
def pseudo_logger(msg):
"""Pseudo-logging function."""
print(msg)
This logger has a first parameter for the logging message, and potentially some other positional parameters and keywords. I want to write a decorater for such a function:
from functools import wraps
def indented(func):
"""Turns the logger func(msg) into an indented logger."""
#wraps(func)
def wrapped(msg, *args, **kwargs):
# ... compute white ...
func(white + msg, *args, **kwargs)
return wrapped
Now I can get a new logger as, for example:
new_logger = intented(pseudo_logger)
Putting it all together gives:
from functools import wraps
import inspect
import re
def indented(func):
"""Turns the logger func(msg) into an indented logger."""
indent = re.compile("^ *")
#wraps(func)
def wrapped(msg, *args, **kwargs):
frame = inspect.currentframe()
context = inspect.getframeinfo(frame.f_back).code_context
firstline = context[0]
match = indent.match(firstline)
white = "." * match.span(0)[1] # Change "." to " "!
func(white + msg, *args, **kwargs)
return wrapped
#indented
def pseudo_logger(msg):
"""Pseudo-logging function."""
print(msg)
def main():
pseudo_logger("This is an indented message!")
if True:
pseudo_logger("Another message, but more indented!")
pseudo_logger("This "
"will ignore the indention of the second "
"or third line.")
if __name__ == "__main__":
main()
I would be hesitant to use this in production code, though. Using code inspection like this is brittle, and depending on where you call this, it might lead to unexpected effects.
I have a very long function func which takes a browser handle and performs a bunch of requests and reads a bunch of responses in a specific order:
def func(browser):
# make sure we are logged in otherwise log in
# make request to /search and check that the page has loaded
# fill form in /search and submit it
# read table of response and return the result as list of objects
Each operation require a large amount of code due to the complexity of the DOM and they tend to grow really fast.
What would be the best way to refactor this function into smaller components so that the following properties still hold:
the execution flow of the operations and/or their preconditions is guaranteed just like in the current version
the preconditions are not checked with asserts against the state, as this is a very costly operation
func can be called multiple times on the browser
?
Just wrap the three helper methods in a class, and track which methods are allowed to run in an instance.
class Helper(object):
def __init__(self):
self.a = True
self.b = False
self.c = False
def funcA(self):
if not self.A:
raise Error("Cannot run funcA now")
# do stuff here
self.a = False
self.b = True
return whatever
def funcB(self):
if not self.B:
raise Error("Cannot run funcB now")
# do stuff here
self.b = False
self.c = True
return whatever
def funcC(self):
if not self.C:
raise Error("Cannot run funcC now")
# do stuff here
self.c = False
self.a = True
return whatever
def func(...):
h = Helper()
h.funcA()
h.funcB()
h.funcC()
# etc
The only way to call a method is if its flag is true, and each method clears its own flag and sets the next method's flag before exiting. As long as you don't touch h.a et al. directly, this ensures that each method can only be called in the proper order.
Alternately, you can use a single flag that is a reference to the function currently allowed to run.
class Helper(object):
def __init__(self):
self.allowed = self.funcA
def funcA(self):
if self.allowed is not self.funcA:
raise Error("Cannot run funcA now")
# do stuff
self.allowed = self.funcB
return whatever
# etc
Here's the solution I came up with. I used a decorator (closely related to the one in this blog post) which only allows for a function to be called once.
def call_only_once(func):
def new_func(*args, **kwargs):
if not new_func._called:
try:
return func(*args, **kwargs)
finally:
new_func._called = True
else:
raise Exception("Already called this once.")
new_func._called = False
return new_func
#call_only_once
def stateA():
print 'Calling stateA only this time'
#call_only_once
def stateB():
print 'Calling stateB only this time'
#call_only_once
def stateC():
print 'Calling stateC only this time'
def state():
stateA()
stateB()
stateC()
if __name__ == "__main__":
state()
You'll see that if you re-call any of the functions, the function will throw an Exception stating that the functions have already been called.
The problem with this is that if you ever need to call state() again, you're hosed. Unless you implement these functions as private functions, I don't think you can do exactly what you want due to the nature of Python's scoping rules.
Edit
You can also remove the else in the decorator and your function will always return None.
Here a snippet I used once for my state machine
class StateMachine(object):
def __init__(self):
self.handlers = {}
self.start_state = None
self.end_states = []
def add_state(self, name, handler, end_state=0):
name = name.upper()
self.handlers[name] = handler
if end_state:
self.end_states.append(name)
def set_start(self, name):
# startup state
self.start_state = name
def run(self, **kw):
"""
Run
:param kw:
:return:
"""
# the first .run call call the first handler with kw keywords
# each registered handler should returns the following handler and the needed kw
try:
handler = self.handlers[self.start_state]
except:
raise InitializationError("must call .set_start() before .run()")
while True:
(new_state, kw) = handler(**kw)
if isinstance(new_state, str):
if new_state in self.end_states:
print("reached ", new_state)
break
else:
handler = self.handlers[new_state]
elif hasattr(new_state, "__call__"):
handler = new_state
else:
return
The use
class MyParser(StateMachine):
def __init__(self):
super().__init__()
# define handlers
# we can define many handler as we want
self.handlers["begin_parse"] = self.begin_parse
# define the startup handler
self.set_start("begin_parse")
def end(self, **kw):
logging.info("End of parsing ")
# no callable handler => end
return None, None
def second(self, **kw):
logging.info("second ")
# do something
# if condition is reach the call `self.end` handler
if ...:
return self.end, {}
def begin_parse(self, **kw):
logging.info("start of parsing ")
# long process until the condition is reach then call the `self.second` handler with kw new keywords
while True:
kw = {}
if ...:
return self.second, kw
# elif other cond:
# return self.other_handler, kw
# elif other cond 2:
# return self.other_handler 2, kw
else:
return self.end, kw
# start the state machine
MyParser().run()
will print
INFO:root:start of parsing
INFO:root:second
INFO:root:End of parsing
You could use local functions in your func function. Ok, they are still declared inside one single global function, but Python is nice enough to still give you access to them for tests.
Here is one example of one function declaring and executing 3 (supposedly heavy) subfunctions. It takes one optional parameter test that when set to TEST prevent actual execution but instead gives external access to individual sub-functions and to a local variable:
def func(test=None):
glob = []
def partA():
glob.append('A')
def partB():
glob.append('B')
def partC():
glob.append('C')
if (test == 'TEST'):
global testA, testB, testC, testCR
testA, testB, testC, testCR = partA, partB, partC, glob
return None
partA()
partB()
partC()
return glob
When you call func, the 3 parts are executed in sequence. But if you first call func('TEST'), you can then access the local glob variable as testCR, and the 3 subfunctions as testA, testB and testC. This way you can still test individually the 3 parts with well defined input and control their output.
I would insist on the suggestion given by #user3159253 in his comment on the original question:
If the sole purpose is readability I would split the func into three "private" > or "protected" ones (i.e. _func1 or __func1) and a private or protected property > which keeps the state shared between the functions.
This makes a lot of sense to me and seems more usual amongst object oriented programming than the other options. Consider this example as an alternative:
Your class (teste.py):
class Test:
def __init__(self):
self.__environment = {} # Protected information to be shared
self.public_stuff = 'public info' # Accessible to outside callers
def func(self):
print "Main function"
self.__func_a()
self.__func_b()
self.__func_c()
print self.__environment
def __func_a(self):
self.__environment['function a says'] = 'hi'
def __func_b(self):
self.__environment['function b says'] = 'hello'
def __func_c(self):
self.__environment['function c says'] = 'hey'
Other file:
from teste import Test
t = Test()
t.func()
This will output:
Main function says hey guys
{'function a says': 'hi', 'function b says': 'hello', 'function c says': 'hey'}
If you try to call one of the protected functions, an error occurs:
Traceback (most recent call last):
File "C:/Users/Lucas/PycharmProjects/testes/other.py", line 6, in <module>
t.__func_a()
AttributeError: Test instance has no attribute '__func_a'
Same thing if you try to access the protected environment variable:
Traceback (most recent call last):
File "C:/Users/Lucas/PycharmProjects/testes/other.py", line 5, in <module>
print t.__environment
AttributeError: Test instance has no attribute '__environment'
In my view this is the most elegant, simple and readable way to solve your problem, let me know if it fits your needs :)
How could I inspect the values of function arguments inside the sys.settrace call? It seems that I have possibility to output pretty much everything (lines, filenames, stacktraces, return values, etc) except arguments. Is there a workaround that would allow me to track function argument values as well?
You can use the combination of Code Objects and Frame Objects.
See for the descriptions of these in the Python Data-Model Reference.
import sys
def fn(frame, msg, arg):
if msg != 'call': return
# Filter as appropriate
if frame.f_code.co_filename.startswith("/usr"): return
print("Called", frame.f_code.co_name)
for i in range(frame.f_code.co_argcount):
name = frame.f_code.co_varnames[i]
print(" Argument", name, "is", frame.f_locals[name])
sys.settrace(fn)
def hai(a, b, c):
print(a, b, c)
hai("Hallo", "Welt", "!")
The crucial thing to realize is that
we can see all local variables in the frame as f_locals.
We can extract the names of the variables in the parameter list from f_code.co_varnames.
I turned Marcs answer into a script which can be used for inspecting other scripts:
print_func_calls.py:
#!/usr/bin/env python
import sys
# opt-out file names which start with one of these prefixes
FILENAME_FILTER = {"/usr", "<"}
# opt-in file names again which match one of these prefixes
FILENAME_UNFILTER = {"/lib/python/some-important-module"}
# opt-out function names
FN_NAME_FILTER = {"<module>", "__new__", "__setattr__", "<lambda>"}
def to_str(exp):
"""Turn an argument value into a string without dying on exceptions"""
try:
return repr(exp)[:100]
except Exception as exc:
return "n/a"
def fn(frame, msg, arg):
if msg != 'call':
return
filename, fn_name = frame.f_code.co_filename, frame.f_code.co_name
if (not all(not filename.startswith(p) for p in FILENAME_FILTER) and
all(not filename.startswith(p) for p in FILENAME_UNFILTER) or
fn_name in FN_NAME_FILTER):
return
argstr = ", ".join("%s=%s" % (
frame.f_code.co_varnames[i], to_str(frame.f_locals[frame.f_code.co_varnames[i]]))
for i in range(frame.f_code.co_argcount))
print(">>> %s::\033[37m%s\033[0m(%s)" % (filename, fn_name, argstr))
sys.settrace(fn)
sys.argv = sys.argv[1:]
exec(open(sys.argv[0]).read())
Use it like this:
print_func_calls.py my-script.py arg1..argN
I've been using the following code to trace the execution of my programs:
import sys
import linecache
import random
def traceit(frame, event, arg):
if event == "line":
lineno = frame.f_lineno
filename = frame.f_globals["__file__"]
if filename == "<stdin>":
filename = "traceit.py"
if (filename.endswith(".pyc") or
filename.endswith(".pyo")):
filename = filename[:-1]
name = frame.f_globals["__name__"]
line = linecache.getline(filename, lineno)
print "%s:%s:%s: %s" % (name, lineno,frame.f_code.co_name , line.rstrip())
return traceit
def main():
print "In main"
for i in range(5):
print i, random.randrange(0, 10)
print "Done."
sys.settrace(traceit)
main()
Using this code, or something like it, is it possible to report the values of certain function arguments? In other words, the above code tells me "which" functions were called and I would like to know "what" the corresponding values of the input variables for those function calls.
Thanks in advance.
The traceit function that you posted can be used to print information as each line of code is executed. If all you need is the function name and arguments when certain functions are called, I would suggest using this trace decorator instead:
import functools
def trace(f):
'''This decorator shows how the function was called'''
#functools.wraps(f)
def wrapper(*arg,**kw):
arg_str=','.join(['%r'%a for a in arg]+['%s=%s'%(key,kw[key]) for key in kw])
print "%s(%s)" % (f.__name__, arg_str)
return f(*arg, **kw)
return wrapper
You could use it as follows:
#trace
def foo(*args,**kws):
pass
foo(1)
# foo(1)
foo(y=1)
# foo(y=1)
foo(1,2,3)
# foo(1,2,3)
Edit: Here is an example using trace and traceit in conjunction:
Below, trace is used in 2 different ways. The normal way is to decorate functions you define:
#trace
def foo(i):
....
But you can also "monkey-patch" any function whether you defined it or not like this:
random.randrange=trace(random.randrange)
So here's the example:
import sys
import linecache
import random
import functools
def trace(f):
'''This decorator shows how the function was called'''
#functools.wraps(f)
def wrapper(*arg,**kw):
arg_str=','.join(['%r'%a for a in arg]+['%s=%s'%(key,kw[key]) for key in kw])
print "%s(%s)" % (f.__name__, arg_str)
return f(*arg, **kw)
return wrapper
def traceit(frame, event, arg):
if event == "line":
lineno = frame.f_lineno
filename = frame.f_globals["__file__"]
if filename == "<stdin>":
filename = "traceit.py"
if (filename.endswith(".pyc") or
filename.endswith(".pyo")):
filename = filename[:-1]
name = frame.f_globals["__name__"]
line = linecache.getline(filename, lineno)
print "%s:%s:%s: %s" % (name, lineno,frame.f_code.co_name , line.rstrip())
return traceit
random.randrange=trace(random.randrange)
#trace
def foo(i):
print i, random.randrange(0, 10)
def main():
print "In main"
for i in range(5):
foo(i)
print "Done."
sys.settrace(traceit)
main()
frame.f_locals will give you the values of the local variables, and I guess you could keep track of the last frame you've seen and if frame.f_back is not the lastframe dump frame.f_locals.
I'd predict though that you're pretty quickly going be snowed under with too much data doing this.
Here's your code modified to do this:
import sys
import linecache
import random
class Tracer(object):
def __init__(self):
self.lastframe = None
def traceit(self, frame, event, arg):
if event == "line":
lineno = frame.f_lineno
filename = frame.f_globals["__file__"]
if filename == "<stdin>":
filename = "traceit.py"
if (filename.endswith(".pyc") or
filename.endswith(".pyo")):
filename = filename[:-1]
name = frame.f_globals["__name__"]
line = linecache.getline(filename, lineno)
if frame.f_back is self.lastframe:
print "%s:%s:%s: %s" % (name, lineno,frame.f_code.co_name , line.rstrip())
else:
print "%s:%s:%s(%s)" % (name, lineno,frame.f_code.co_name , str.join(', ', ("%s=%r" % item for item in frame.f_locals.iteritems())))
print "%s:%s:%s: %s" % (name, lineno,frame.f_code.co_name , line.rstrip())
#print frame.f_locals
self.lastframe = frame.f_back
return self.traceit
def main():
print "In main"
for i in range(5):
print i, random.randrange(0, 10)
print "Done."
sys.settrace(Tracer().traceit)
main()
web.py had a method called "upvars" that did something similar, taking in the variables from the calling frame. Note the comment:
def upvars(level=2):
"""Guido van Rossum sez: don't use this function."""
return dictadd(
sys._getframe(level).f_globals,
sys._getframe(level).f_locals)
What is much more useful to me in tracing than dumping ALL the state of variables at the time of execution is to do an eval of each and every line of code, ie:
for modname in modnames: | for init in ., init, encoding
|
if not modname or '.' in modname: | if not init or '.' in init
continue | continue
|
try: |
ie: where real line of code is on the left and each line of code is on the right. I've implemented this in perl and it is a LIFESAVER there. I'm in the process of trying to implement this in python but I'm not as familiar with the language so it will take a bit of time.
Anyways, if anybody has ideas how to implement this, I'd love to hear them. As far as I can tell, it comes down to this function
interpolate_frame(frame, string)
where is the frame passed to the trace function, and the string is the line of code to be interpolated with variables in the current frame. Then, the above code becomes:
print "%s:%s:%s: %s|%s" % (name, lineno,frame.f_code.co_name,
padded(line.rstrip(),10),padded(interpolate_frame(frame, line.rstrip()),100)
I'm going to try to hack this up, but again, if anybody has ideas on this I'm welcome to hear them.