Декоратор Python для печати каждой строки, выполняемой функцией

Я хочу, для целей отладки, распечатать что-то, относящееся к каждой строке, выполняемой в методе python.

Например, если в строке было какое-то присвоение, я хочу напечатать, какое значение было присвоено этой переменной, а если был вызов функции, я хочу распечатать значение, возвращаемое функцией, и т. Д.

Так, например, если бы я использовал декоратор, примененный к функции / методу, например:

@some_decorator
def testing() : 
    a = 10
    b = 20
    c = a + b
    e = test_function()

функция тестирования при вызове должна напечатать следующее:

a = 10
b = 20  
c = 30
e = some_value

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

Следовательно, я особенно ищу решение с использованием декораторов, так как мне любопытно, можно ли иметь декоратор, который может пройти через всю функцию строка за строкой и украсить ее строка за строкой, но любые и все решения приветствуются.

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


person ironstein    schedule 23.08.2015    source источник
comment
Это могло быть сделано, но я не знаю ни одного. По сути, вы можете извлечь файл и номер строки из декорированной функции, перечитать функцию, скомпилировать ее в AST, вставить узлы в AST, а затем скомпилировать AST и использовать его в качестве функции. Интересный проект.   -  person Patrick Maupin    schedule 23.08.2015
comment
Может быть, вам поможет модуль pdb?   -  person augurar    schedule 23.08.2015
comment
@perreal, не могли бы вы объяснить свой подход более подробно или предоставить ссылку, по которой я мог бы найти больше об элементах вашего подхода и, если возможно, о том, как применить их к моей проблеме. кстати спасибо за ваш ответ   -  person ironstein    schedule 23.08.2015
comment
@ironstein: ознакомьтесь с этим для начала tomforb.es/automatically-inline-python-function- звонки   -  person perreal    schedule 23.08.2015
comment
По сути, вам нужно обернуть свою функцию функцией, которая загружает ее в отладчик, а затем выполнять ее за один шаг.   -  person skyking    schedule 23.08.2015
comment
@skyking, не могли бы вы уточнить, как мне это сделать, поскольку я не знаком с использованием отладчика   -  person ironstein    schedule 23.08.2015
comment
Что за линия? Вы можете иметь выражение, занимающее несколько строк текста, и иметь более одного оператора в строке a=1;b=2. Обратите внимание, что python компилируется в байт-код, и строка может создавать любое количество байт-кодов ... модуль sys позволяет отслеживать вызовы функций, но я считаю, что нет простого способа получить выполнение точно по строкам.   -  person Bakuriu    schedule 28.08.2015
comment
@Bakuriu, ты прав. То, что я пытался сказать, было одним выражением   -  person ironstein    schedule 28.08.2015


Ответы (1)


Как насчет этого? Это сработает для вас?

Контекст отладки:

import sys

class debug_context():
    """ Debug context to trace any function calls inside the context """

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('Entering Debug Decorated func')
        # Set the trace function to the trace_calls function
        # So all events are now traced
        sys.settrace(self.trace_calls)

    def __exit__(self, *args, **kwargs):
        # Stop tracing all events
        sys.settrace = None

    def trace_calls(self, frame, event, arg): 
        # We want to only trace our call to the decorated function
        if event != 'call':
            return
        elif frame.f_code.co_name != self.name:
            return
        # return the trace function to use when you go into that 
        # function call
        return self.trace_lines

    def trace_lines(self, frame, event, arg):
        # If you want to print local variables each line
        # keep the check for the event 'line'
        # If you want to print local variables only on return
        # check only for the 'return' event
        if event not in ['line', 'return']:
            return
        co = frame.f_code
        func_name = co.co_name
        line_no = frame.f_lineno
        filename = co.co_filename
        local_vars = frame.f_locals
        print ('  {0} {1} {2} locals: {3}'.format(func_name, 
                                                  event,
                                                  line_no, 
                                                  local_vars))

Декоратор отладки:

def debug_decorator(func):
    """ Debug decorator to call the function within the debug context """
    def decorated_func(*args, **kwargs):
        with debug_context(func.__name__):
            return_value = func(*args, **kwargs)
        return return_value
    return decorated_func

использование

@debug_decorator
def testing() : 
    a = 10
    b = 20
    c = a + b

testing()

Вывод

###########################################################
#output:
#   Entering Debug Decorated func
#     testing line 44 locals: {}
#     testing line 45 locals: {'a': 10}
#     testing line 46 locals: {'a': 10, 'b': 20}
#     testing return 46 locals: {'a': 10, 'b': 20, 'c': 30}
###########################################################
person ashwinjv    schedule 28.08.2015
comment
Это так удобно, это должно быть отмечено как правильный ответ. - person DimmuR; 30.12.2016
comment
Как это расширить для методов класса? - person neok; 15.02.2017