Как использовать 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()

Используя этот код или что-то подобное, можно ли сообщить значения определенных аргументов функции? Другими словами, приведенный выше код сообщает мне, «какие» функции были вызваны, и я хотел бы знать, «какие» соответствующие значения входных переменных для этих вызовов функций.

Заранее спасибо.


person reckoner    schedule 11.04.2010    source источник


Ответы (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()
person Duncan    schedule 11.04.2010
comment
Не могли бы вы пояснить свой комментарий на примере. Я не слежу за тобой. - person reckoner; 13.04.2010
comment
Нет проблем, я добавил пример - person Duncan; 14.04.2010

Опубликованную вами функцию 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 используется двумя разными способами. Обычный способ — декорировать определяемые вами функции:

@trace
def foo(i):
    ....

Но вы также можете «обезьянье-заплатить» любую функцию, независимо от того, определили вы ее или нет:

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()
person unutbu    schedule 11.04.2010
comment
Это удобно, но не работает с кодом трассировки в исходном посте, поэтому не улучшает отчет показанной функции traceit. - person reckoner; 13.04.2010
comment
@reckoner: Ваш вопрос был задан, используя этот код или что-то в этом роде, поэтому я решил, что вы не стремились использовать эту функцию traceit. Более того, можно украшать функции @trace и одновременно использовать traceit. - person unutbu; 13.04.2010
comment
извините за путаницу. Думаю, я больше привержен traceit, чем думал. Как я могу использовать декоратор, как вы описываете с помощью traceit? - person reckoner; 13.04.2010

В 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)
person Frederik    schedule 11.04.2010

Что для меня гораздо полезнее при трассировке, чем сброс ВСЕХ состояний переменных во время выполнения, так это выполнение оценки каждой строки кода, то есть:

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, и там это спасает. Я пытаюсь реализовать это на 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)

Я собираюсь попытаться взломать это, но опять же, если у кого-то есть идеи по этому поводу, я буду рад их услышать.

person user534463    schedule 08.12.2010