Как использовать traceit для сообщения о входных переменных функций в трассировке стека
Я использовал следующий код для отслеживания выполнения моих программ:
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()
Используя этот код или что-то подобное, можно ли сообщать значения определенных аргументов функции? Другими словами, приведенный выше код сообщает мне "какие" функции были вызваны, и я хотел бы знать, "что" соответствующие значения входных переменных для этих вызовов функций.
Заранее спасибо.
4 ответа
frame.f_locals выдаст вам значения локальных переменных, и я думаю, вы могли бы отслеживать последний просмотренный кадр, и если frame.f_back не является дампом последнего кадра frame.f_locals.
Я бы предсказал, однако, что вы довольно быстро будете засыпаны слишком большим количеством данных, делающих это.
Вот ваш код, модифицированный для этого:
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()
Отправленная вами функция traceit может использоваться для печати информации при выполнении каждой строки кода. Если все, что вам нужно, это имя функции и аргументы при вызове определенных функций, я бы предложил использовать этот декоратор трассировки:
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
Вы можете использовать его следующим образом:
@trace
def foo(*args,**kws):
pass
foo(1)
# foo(1)
foo(y=1)
# foo(y=1)
foo(1,2,3)
# foo(1,2,3)
Изменить: Вот пример использования trace
а также traceit
в сочетании: ниже, trace
используется 2 разными способами. Обычный способ - декорировать определенные вами функции:
@trace
def foo(i):
....
Но вы также можете "monkey-patch" любой функции, независимо от того, определили вы это или нет так:
random.randrange=trace(random.randrange)
Итак, вот пример:
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()
У web.py был метод под названием "upvars", который делал нечто подобное, принимая переменные из вызывающего фрейма. Обратите внимание на комментарий:
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)
Что гораздо полезнее для меня при трассировке, чем вывод ВСЕХ состояний переменных во время выполнения, - это выполнить оценку каждой строки кода, то есть:
for modname in modnames: | for init in ., init, encoding
|
if not modname or '.' in modname: | if not init or '.' in init
continue | continue
|
try: |
то есть: где реальная строка кода находится слева, а каждая строка кода справа. Я реализовал это в Perl, и это LIFESAVER там. Я пытаюсь реализовать это на python, но я не так хорошо знаком с языком, так что это займет немного времени.
В любом случае, если у кого-то есть идеи, как это реализовать, я бы хотел их услышать. Насколько я могу судить, это сводится к этой функции
interpolate_frame(frame, string)
где - кадр, переданный функции трассировки, а строка - строка кода, которая должна быть интерполирована с переменными в текущем кадре. Затем приведенный выше код становится:
print "%s:%s:%s: %s|%s" % (name, lineno,frame.f_code.co_name,
padded(line.rstrip(),10),padded(interpolate_frame(frame, line.rstrip()),100)
Я попытаюсь разобраться с этим, но опять же, если у кого-то есть идеи по этому поводу, я могу их услышать.