Python Decorator for printing every line executed

2020-05-19 02:37发布

问题:

I want to, for debugging purposes, print out something pertaining to each and every line executed in a python method.

For example if there was some assignment in the line, i want to print what value was assigned for that variable, and if there was a function call, i want to print out the value returned by the function, etc.

So, for example if i were to use a decorator, applied on function/method such as :

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

the function testing when called, should print the following :

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

Is there some way to achieve this? More fundamentally, i want to know whether i can write a code that can go through some other code line by line, check what type of an instruction it is, etc. Or maybe like we can get a dictionary for finding out all the variables in a class, can i get a dictionary like datastructure for getting every instruction in a function, which is as good a metaprogram can get.

Hence, I am particularly looking a solution using decorators, as I am curious if one can have a decorator that can go through an entire function line by line, and decorate it line by line, but any and all solutions are welcome.

Thanks in advance.

回答1:

How about something like this? Would this work for you?

Debug Context:

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))

Debug Decorator:

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

Usage

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

testing()

Output

###########################################################
#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}
###########################################################