2

I want to have a decorator, that takes only *args and **kwargs as input, makes some changes to them and then call the original function with positional and named arguments from the decorated function unchanged.

The change to the arguments is just to prepend -- to the args and also to the keys of the dict of kwargs.

E.g. I want to use this decorator like this:

from decorator import decorator

@decorator
def prepare_opts(decorated_func, *args, **kwargs):
    prepared_args = prepare_single_opt_keys(args)
    prepared_kwargs = prepare_named_opt_keys(kwargs)

    return decorated_func(*prepared_args, **prepared_kwargs)

@my_decorator
def func1(pos1, pos2, *args, named1=None, **kwargs):
    ...do stuff

Here pos1, pos2 and named1 should be ignored by the decorator.

So a call to func1 like this:

func1('foo', 'bar', 'foo', 'bar', named1='foobar', foo='bar')

Should call the decorated function like this:

func1('foo', 'bar', named1='foobar', ('--foo', '--bar'), {'--foo': 'bar'})

But this can obviously not work, as the decorator passes ('--foo', '--bar', '--foo', '--bar') to pos1 and {'--named1': 'foobar', '--foo': 'bar'} to pos2.

I know that I can get the correct arguments if I change the decorator to:

@decorator
def prepare_opts(decorated_func, pos1, pos2, *args, named1, **kwargs):
    prepared_args = prepare_single_opt_keys(args)
    prepared_kwargs = prepare_named_opt_keys(kwargs)

    return decorated_func(pos1, pos2, *prepared_args, named1, **prepared_kwargs)

But the problem here is, I want the decorator to work with a lot of different functions, that all have a different number of positional and named parameters and additional *args and **kwargs.

Summary of my question: Is there a way to parse *args and **kwargs in a decorator without touching any of the positional and named arguments of the decorated function and without consuming those in the *args/**kwargs-Arguments of my Decoratorfunction?

Igl3
  • 4,900
  • 5
  • 35
  • 69
  • I haven't really delved into whether these ([1](https://stackoverflow.com/questions/147816/preserving-signatures-of-decorated-functions), [2](https://stackoverflow.com/questions/18906760/parsing-args-and-kwargs-in-decorators)) contain the exact solution to your problem, but at the very least they are related and could provide some valuable insights. In particular, it's worth at least checking out the [`decorator`](https://pypi.python.org/pypi/decorator) module. – John Y Oct 24 '17 at 15:38

1 Answers1

1

You can try inspect.getargspec function. I didn't test it but you can use this approach.

import inspect

def your_decorator(func):
    func_args = inspect.getargspec(func)[0]
    args_number = len(func_args)

    def wrapper(*args, **kwargs):
        if len(args) > args_number:
            args[args_number:] = prepare_single_opt_keys(args[args_number:])
        optional_kwargs = {k: kwargs.pop(k) for k in kwargs if k not in func_args}
        prepared_kwargs = prepare_named_opt_keys(optional_kwargs)
        kwargs.update(prepared_kwargs)
        return func(*args, **kwargs)
    return wrapper

But I am pretty sure that you don't need it. This kind of introspection should and can be almost always avoided.

Raz
  • 7,508
  • 4
  • 27
  • 25
  • A slightly adjusted version of this worked for me. I still wait 1-2 days if there is another "best practice" to do this as you say it can be almost always avoided to use introspection. If no better idea pops up, I gonna accept your answer and add the changes I made to make it work with python3 – Igl3 Oct 25 '17 at 13:14
  • 1
    From the inspect package getargspec: `This function is deprecated, as it does not support annotations or keyword-only parameters and will raise ValueError if either is present on the supplied callable.` – Igl3 Oct 25 '17 at 14:37