Как использовать traceit для сообщения о входных переменных функций в трассировке стека - PullRequest
1 голос
/ 11 апреля 2010

Я использовал следующий код для отслеживания выполнения моих программ:

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 ]

3 голосов
/ 11 апреля 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()
2 голосов
/ 11 апреля 2010

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()
1 голос
/ 11 апреля 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)
0 голосов
/ 08 декабря 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, и это 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)

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

...