简体   繁体   中英

Function decorator that logs the value of specified function arguments in an accessible python object

I am trying to create a function decorator that logs the value specified function arguments in an accessible python object. I have already working code but I am missing a piece to finish this up.

First, I have the object log where I will save stuff correctly set up:

class Borg:
    _shared_state = {}

    def __init__(self):
        self.__dict__ = self._shared_state

class Log(Borg):

    def __init__(self):
        Borg.__init__(self)
        if not hasattr(self, 'tape'):
            self.tape = []

    def add(self, this):
        self.tape.append(this)

    def __str__(self):
        return '\n'.join([str(line) for line in self.tape])

Then I have a generic call object and the decorator implementation (with missing code):

import inspect
import functools

class Call:

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

    def __str__(self):
        return f'Call(name={self.name}, saved_arguments={self.saved_arguments})'

def record(func, save_args_names=None):
    if save_args_names is None:
        save_args_names = {}
    name = func.__name__
    args = inspect.getfullargspec(func).args
    if save_args_names and not set(save_args_names).issubset(set(args)):
        raise ValueError(f'Arguments not present in function: {set(save_args_names) - set(args)}')
    log = Log()

    @functools.wraps(func)
    def wrapper(*func_args, **func_kwargs):
        # **here** I am missing something to replace 0 with the correct values!
        saved_arguments = {a: 0 for a in save_args_names}
        log.add(Call(name, **saved_arguments))
        return_value = func(*func_args, **func_kwargs)
        return return_value

    return wrapper

To test this, I have the following functions set up:

def inner(x, add=0):
    return sum(x) + add

def outer(number, add=0):
    x = range(number)
    return inner(x, add)

and the basic use case (no saving of arguments) works:

inner = record(inner)
print(outer(1), outer(2), outer(3))
print(Log())

It outputs, correctly:

0 1 3
Call(name=inner, saved_arguments={})
Call(name=inner, saved_arguments={})
Call(name=inner, saved_arguments={})

What I am missing is a way to have this use case:

inner = record(inner, save_args_names=['x'])
print(outer(1), outer(2), outer(3))
print(Log())

to output:

0 1 3
Call(name=inner, saved_arguments={'x': range(0, 1)})
Call(name=inner, saved_arguments={'x': range(0, 2)})
Call(name=inner, saved_arguments={'x': range(0, 3)})

This, should also work for keyword arguments, eg:

inner = record(inner, save_args_names=['x', 'add'])
print(outer(1, 2), outer(2, 3), outer(3, 4))
print(Log())

should output:

2 4 7
Call(name=inner, saved_arguments={'x': range(0, 1), 'add': 2})
Call(name=inner, saved_arguments={'x': range(0, 2), 'add': 3})
Call(name=inner, saved_arguments={'x': range(0, 3), 'add': 4})

I feel like I am close and that the inspect library should help me close this, but a little help would be much appreciated!

The function you're looking for is Signature.bind . Define your wrapper function like so:

@functools.wraps(func)
def wrapper(*func_args, **func_kwargs):
    signature = inspect.signature(func)
    bound_args = signature.bind(*func_args, **func_kwargs)
    saved_arguments = {a: bound_args.arguments[a] for a in save_args_names}

    log.add(Call(name, **saved_arguments))
    return_value = func(*func_args, **func_kwargs)
    return return_value

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM