0

Is there a way to record variables & arguments of a program in Python, without manually decorate the functions in it? For example, given the following code:

def get_b(a):
    # do something with a
    # ...

    b = 3
    return b

def get_a():
    a = 2
    return a

def foo():
    a = get_a()
    b = get_b(a)
    return a, b

if __name__ '__main__':
    a, b = foo()

I'd like to know what were the values of the arguments/variables in that particular run. Maybe something like this:

function get_a:
variables: "a" = 2

function get_b:
parameters: "a" = 2
variables: "b" = 3

Is there a way to "record" all of this information?

David Lasry
  • 829
  • 3
  • 12
  • 31

1 Answers1

0

Add the following code in your module's global scope. It will give what you asked for. The code makes use of sys.setprofile. Make sure to consult the documentation. setprofile is not meant for everyday usage. It can cause performance degradation and my sample code would not work in multi-threaded programs.

What we are doing is for every statement which calls or returns a function our profile function is invoked. In this profile function we have access to the event type (call, return) and frame info object. From frame info object we can access all the variables in the namespace of the invoking or returning function. From those local variables we filter those variables which were passed as parameters to the respective function. That's just it.

import inspect

def profile(frame, event, arg):
    if (fn_name := frame.f_code.co_name) != '<module>' and event == 'return':
        args_info = inspect.getargvalues(frame)
        args_locals = args_info.locals
        params = [f'{arg}={args_locals.pop(arg)}' for arg in args_info.args]

        if not params:
            params = ''
        else:
            params = ', '.join(params)
   
        if args_locals:
            args_locals = ', '.join([f'{k}={v}' for (k, v) in args_locals.items()])
        else:
            args_locals = ''

        print(f'Function {fn_name}:')
        print(f'    params: {params}')
        print(f'    locals: {args_locals}')


sys.setprofile(profile)

Output:

Function get_a:
    params: 
    locals: a=2
Function get_b:
    params: a=2
    locals: b=3
Function foo:
    params: 
    locals: a=2, b=3

Suggested reading: What cool hacks can be done using sys.settrace?

user47
  • 1,080
  • 11
  • 28