Is there a way to get the trace table for a Python program? Or for a program to run another program and get its trace table? I'm a teacher trying to flawlessly verify the answers to the tracing problems that we use on our tests.
So, for example, assuming I have a Python program named problem1.py
with the following content:
problem1.py
a = 1
b = 2
a = a + b
Executing the presumed program traceTable.py
should go as:
$ python traceTable.py problem1.py
L || a | b
1 || 1 |
2 || 1 | 2
4 || 3 | 2
(Or the same information with a different syntax)
I've looked into the trace
module, and I can't see a way that it supports this.
Updated
Ladies and gentlemen: using Ned Batchelder's excellent advice, I give you traceTable.py
!
Well.. almost. As you can see in Ned Batchelder's example, frame.f_lineno
doesn't always behave intuitively (e.g. both lines 3 & 4 are counted as line 4), but the line numbers are close enough for a fairly good reference. Also, all calculations are correct.
I have tested this with a long program containing an if
statement and it gave the correct table (sans the line numbers).
You will also notice that my program is significantly longer than Ned Batchelder's proof of concept due to accounting for the "more interesting ecosystems of data" in larger programs he mentioned. In the scope of using execfile
and all the variables needed to manage it and reduce noise (ala ignored_variables
) as well as produce proper string output, a lot more code is needed:
traceTable.py
'''
Usage: python traceTable.py program
-program Python program to be traced
'''
import sys
if len(sys.argv) < 2:
print __doc__
exit()
else:
file_name = sys.argv[1]
past_locals = {}
variable_list = []
table_content = ""
ignored_variables = set([
'file_name',
'trace',
'sys',
'past_locals',
'variable_list',
'table_content',
'getattr',
'name',
'self',
'object',
'consumed',
'data',
'ignored_variables'])
def trace(frame, event, arg_unused):
global past_locals, variable_list, table_content, ignored_variables
relevant_locals = {}
all_locals = frame.f_locals.copy()
for k,v in all_locals.items():
if not k.startswith("__") and k not in ignored_variables:
relevant_locals[k] = v
if len(relevant_locals) > 0 and past_locals != relevant_locals:
for i in relevant_locals:
if i not in past_locals:
variable_list.append(i)
table_content += str(frame.f_lineno) + " || "
for variable in variable_list:
table_content += str(relevant_locals[variable]) + " | "
table_content = table_content[:-2]
table_content += '\n'
past_locals = relevant_locals
return trace
sys.settrace(trace)
execfile(file_name)
table_header = "L || "
for variable in variable_list:
table_header += variable + ' | '
table_header = table_header[:-2]
print table_header
print table_content
When called, it produces the output
$ python traceTable.py problem1.py
L || a | b
2 || 1
4 || 1 | 2
4 || 3 | 2