3

I have a function

def func(a,b,c,d):
    ...

and I am trying to write a decorator that understands the arguments and logs some of them to a different system.

def decorator(func):
    def new_func(*args, **kwargs):
        if (func.__name__ == 'func'):
            a = ?
            b = ?
            c = ?
            d = ?
        else:
            a = ?
            b = ?
            c = ?
            d = ?
        log_to_system(a, b, c, d)
        return func(*args, **kwargs)
return new_func

The problem is that the decorator doesn't have an easy way to extract the a,b,c,d values from both the args and the kwargs since the user can pass these in using either positional or keyword arguments. I would also like to keep this generic since this decorator could be used on various different functions.

Is there a library or a utility that can extract the values of the parameters from args and kwargs easily?

6
  • Is func(2,3,4,a=1) a valid way to use func? If so is b equal to 2? Commented Feb 22, 2019 at 19:08
  • 1
    Which four arguments are you looking for in new_func? Are you sure new_func needs to be that general, if you have 4 specific arguments in mind? Commented Feb 22, 2019 at 19:08
  • @chepner yes, it needs to be general since I need to use this decorator on different functions, and extract subsets of the parameters which I can log to a system. I don't want to log everything. Commented Feb 22, 2019 at 19:11
  • 1
    Sounds like a bad idea to have a decorator behave differently for different functions IMO. Could you use two different decorators, or use a decorator that takes in an argument to tell it which style to use (and thus which wrapped function to return)? Commented Feb 22, 2019 at 19:12
  • You are probably going to have to get out the inspect module and start digging around in the func argument to the decorator. Commented Feb 22, 2019 at 19:16

3 Answers 3

2

A simple approach is to make your log_to_system function accept variable parameters and variable keyword parameters in addition to the known parameters that it will actually log, so that you can simply pass on the variable arguments and variable keyword arguments from the decorated function to log_to_system and let the interpreter extract the parameters a, b, c and d for you:

def log_to_system(a, b, c, d, *args, **kwargs):
    print(a, b, c, d)

def decorator(func):
    def new_func(*args, **kwargs):
        log_to_system(*args, **kwargs)
        return func(*args, **kwargs)
    return new_func

@decorator
def func(a, b, c, d, e):
    pass

func(1, 2, c=3, d=4, e=5)

This outputs:

1 2 3 4

Alternatively, you can use inspect.signature to obtain a dict of arguments after binding the given variable arguments and keyword arguments to the decorated function's signature, so that you can call log_to_system with just the parameters it needs:

import inspect

def log_to_system(a, b, c, d):
    print(a, b, c, d)

def decorator(func):
    sig = inspect.signature(func)
    def new_func(*args, **kwargs):
        arguments = sig.bind(*args, **kwargs).arguments
        log_to_system(**{k: arguments[k] for k in log_to_system.__code__.co_varnames})
        return func(*args, **kwargs)
    return new_func

@decorator
def func(a, b, c, d, e):
    pass

func(1, 2, c=3, d=4, e=5)

This outputs:

1 2 3 4
Sign up to request clarification or add additional context in comments.

1 Comment

This is an interesting idea. I will accept this solution for now since it solves my problem.
1

You can use inspect, working example:

import inspect


def deco(func):
    signature = inspect.signature(func)

    def _deco(*args, **kwargs):
        binded = signature.bind(*args, **kwargs)
        arguments = binded.arguments  # OrderedDict
        print(arguments.items())
        return func(*args, **kwargs)

    return _deco


@deco
def foo(a, b, c, d):
    pass


@deco
def bar(d, c, b, a):
    pass


foo(1, 2, c=3, d=4)  # odict_items([('a', 1), ('b', 2), ('c', 3), ('d', 4)])
bar(1, a=2, b=3, c=4)  # odict_items([('d', 1), ('c', 4), ('b', 3), ('a', 2)])

Comments

0

You can use this general decorator to dump the parameter values:

def dump_args(func):
    # Decorator to print function call details - parameters names and effective values
    def wrapper(*func_args, **func_kwargs):
        arg_names = func.__code__.co_varnames[:func.__code__.co_argcount]
        args = func_args[:len(arg_names)]
        defaults = func.__defaults__ or ()
        args = args + defaults[len(defaults) - (func.__code__.co_argcount - len(args)):]
        params = list(zip(arg_names, args))
        args = func_args[len(arg_names):]
        if args: params.append(('args', args))
        if func_kwargs: params.append(('kwargs', func_kwargs))
        return func(*func_args, **func_kwargs)
    return wrapper

Credits belong to @aliteralmind, he suggested this decorator in this StackOverflow post.

2 Comments

If the solution is just to directly use a result from another SO question, it would be better to mark this question as a duplicate rather than providing a duplicate answer.
Thanks, I added a duplicate flag.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.