Таблица трассировки для программ Python
Есть ли способ получить таблицу трассировки для программы Python? Или для программы для запуска другой программы и получения ее таблицы трассировки? Я учитель, пытающийся безупречно проверить ответы на проблемы трассировки, которые мы используем в наших тестах.
Итак, например, если у меня есть программа Python с именем problem1.py
со следующим содержимым:
problem1.py
a = 1
b = 2
a = a + b
Выполнение предполагаемой программы traceTable.py
должно выглядеть следующим образом:
$ python traceTable.py problem1.py
L || a | b
1 || 1 |
2 || 1 | 2
4 || 3 | 2
(Или такая же информация с другим синтаксисом)
Я просмотрел модуль trace
, и я не вижу способа его поддержки.
Обновление
Дамы и господа: используя превосходный совет Нед Батчелдер, я даю вам traceTable.py
!
Ну.. почти. Как вы можете видеть в примере Ned Batchelder, frame.f_lineno
не всегда ведет себя интуитивно (например, обе строки 3 и 4 подсчитываются как строка 4), но номера строк достаточно близки для довольно хорошей ссылки. Кроме того, все вычисления верны.
Я тестировал это с помощью длинной программы, содержащей оператор if
, и он дал правильную таблицу (без номеров строк).
Вы также заметите, что моя программа значительно дольше, чем доказательство концепции Ned Batchelder из-за учета "более интересных экосистем данных" в более крупных программах, о которых он упомянул. В области использования execfile
и всех переменных, необходимых для управления им и уменьшения шума (ala ignored_variables
), а также для создания правильного вывода строки требуется намного больше кода:
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
При вызове он выдает вывод
$ python traceTable.py problem1.py
L || a | b
2 || 1
4 || 1 | 2
4 || 3 | 2
Ответы
Ответ 1
Это не тот случай, когда поддерживает текущий инструмент трассировки Python, но его можно создать. Я не знаю, как вы решаете, какие столбцы вывести. В вашем примере a и b являются единственными локальными переменными, но более крупные программы будут иметь более интересные экосистемы данных.
Обновлено: здесь простое доказательство концепции:
1 import sys
2
3 def trace(frame, event, arg_unused):
4 print event, frame.f_lineno, frame.f_locals
5 return trace
6
7 sys.settrace(trace)
8
9 def foo():
10 a = 1
11 b = 2
12
13 a = a + b
14
15 foo()
при запуске вывод:
call 9 {}
line 10 {}
line 11 {'a': 1}
line 13 {'a': 1, 'b': 2}
return 13 {'a': 3, 'b': 2}
Ответ 2
Вы можете использовать отладчик Python, хотя я не знаю, как это сделать на своем собственном, но, скорее всего, возможно, вы можете просто проанализировать вывод.
Вот действительно грубый пример:
adding.py
a = 1
b = 2
a = a + b
запустить его...
PS >python -m pdb adding.py
> adding.py(1)<module>()
-> a = 1
(Pdb) alias stepprint step;;print a;;print b
(Pdb) stepprint
> adding.py(2)<module>()
-> b = 2
1
*** NameError: name 'b' is not defined
(Pdb) stepprint
> adding.py(4)<module>()
-> a = a + b
1
2
(Pdb) stepprint
--Return--
> adding.py(4)<module>()->None
-> a = a + b
3
2
(Pdb) stepprint
--Return--
> <string>(1)<module>()->None
3
2
(Pdb) stepprint
The program finished and will be restarted
> adding.py(1)<module>()
-> a = 1
*** NameError: name 'a' is not defined
*** NameError: name 'b' is not defined
(Pdb) q
PS >
Конец (q) в бит "Готово к программе".
Ответ 3
Основываясь на том, что предложил ned-batchelder, как учитель, я создал класс Tracer
, который помогает при создании LaTeX
outputed longtable
, показывая трассировку программы с выборочными переменными, минуя input()
для (особенно при вызове макросом \bash
из мощного пакета bashful LaTeX
).
tracer.py:
import sys
class Tracer():
def __init__(self, varList=[], startLine=1, jeuEssai=[]):
"""
Arguments :
\tvarList\ttraced variable list (used as column header)
\tstartLine\toffset numbering line from the beginning of the program
\tjeuEssai\tinput values to be sent to the automated input bypass
"""
self.traced_variables = varList
self.traced_line_start = startLine
self.input_values = jeuEssai
self.input_cursor = int(0)
self.traced_variables_new_values = dict( (k, '') for k in self.traced_variables)
print("\\begin{longtable}{c*{%i}{>{\\ttfamily}c}}" % len(self.traced_variables), file=sys.stderr, flush=True)
print("\t\\hline\\no ligne",end='', file=sys.stderr)
for header in self.traced_variables:
print(" &", header,end='', file=sys.stderr)
print(" \\\\ \\hline", file=sys.stderr)
sys.settrace(self.tracer_programme_latex)
def tracer_programme_latex(self, frame, event, args):
if frame.f_code.co_name not in ['input','print','close']:
if event == "line":
output = str()
for var in self.traced_variables:
current_val = str(frame.f_locals.get(var, "-"))
if str(self.traced_variables_new_values.get(var, "-")) != current_val:
self.traced_variables_new_values[var] = current_val
current_val = "\hit{}" + current_val
output += " & "
output += current_val
output += " \\\\"
print("\t%s%s" % (str(frame.f_lineno - self.traced_line_start), output), file=sys.stderr, flush=True)
return self.tracer_programme_latex
def close(self):
"""Close the 'longtable' LaTeX environnement."""
print("\\end{longtable}", file=sys.stderr, flush=True)
def input(self, prompt=None):
"""
bypass de la fonction 'input()' pour injecter
les valeurs d'essais.
Le jeu d'essai est fourni de manière cyclique. Cela peut
causer des boucles infinies si vous ne fournissez pas une
valeur permettant de réaliser l'arrêt des entrées (dans le
cas bien-sûr où 'input()' est appelé dans une boucle).
"""
self.input_cursor = (1 + self.input_cursor) % len(self.input_values)
return self.input_values[self.input_cursor - 1]
def print(self, *args):
pass
Затем вы можете найти пример, и полученный результат:
program.py:
def factor():
question = "Give a number: "
number = float(input(question))
product = 1
while number != 0 :
product *= number
print("Product:", product)
number = float(input(question))
if __name__ == "__main__":
import sys
TRACING = len(sys.argv) == 2 and sys.argv[1] == 'trace'
if TRACING:
from tracer import Tracer
t = Tracer(varList=['question','number','product'], startLine=2, jeuEssai=[7,6,5,-8,0])
input = t.input
factor()
if TRACING:
t.close()
стандартный вывод: (вызывается python3 program.py
)
Give a number: 7
Product: 7.0
Give a number: 6
Product: 42.0
Give a number: 5
Product: 210.0
Give a number: -8
Product: -1680.0
Give a number: 0
вывод с помощью Tracer: (при вызове python3 program.py trace 1>/dev/null
)
\begin{longtable}{c*{3}{>{\ttfamily}c}}
\hline\no ligne & question & number & product \\ \hline
0 & \hit{}- & \hit{}- & \hit{}- \\
1 & \hit{}Give a number: & - & - \\
2 & Give a number: & \hit{}7.0 & - \\
3 & Give a number: & 7.0 & \hit{}1 \\
4 & Give a number: & 7.0 & 1 \\
5 & Give a number: & 7.0 & \hit{}7.0 \\
6 & Give a number: & 7.0 & 7.0 \\
3 & Give a number: & \hit{}6.0 & 7.0 \\
4 & Give a number: & 6.0 & 7.0 \\
5 & Give a number: & 6.0 & \hit{}42.0 \\
6 & Give a number: & 6.0 & 42.0 \\
3 & Give a number: & \hit{}5.0 & 42.0 \\
4 & Give a number: & 5.0 & 42.0 \\
5 & Give a number: & 5.0 & \hit{}210.0 \\
6 & Give a number: & 5.0 & 210.0 \\
3 & Give a number: & \hit{}-8.0 & 210.0 \\
4 & Give a number: & -8.0 & 210.0 \\
5 & Give a number: & -8.0 & \hit{}-1680.0 \\
6 & Give a number: & -8.0 & -1680.0 \\
3 & Give a number: & \hit{}0.0 & -1680.0 \\
\end{longtable}
Макрос \hit{}
вставляется, когда значение изменено. Это зависит от вас, чтобы определить что-то релевантное, например, цветной макрос: \newcommand{\hit}{\color{red}}