Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python create decorator preserving function arguments

Tags:

python

I'm trying to write a decorator that preserves the arguments of the functions it decorates. The motivation for doing this is to write a decorator that interacts nicely with pytest.fixtures.

Suppose we have a function foo. It takes a single argument a.

def foo(a):
    pass

If we get the argument spec of foo

>>> inspect.getargspec(foo)
ArgSpec(args=['a'], varargs=None, keywords=None, defaults=None)

We frequently want to create a decorator where the wrapper function passes all of its arguments verbatim to the wrapped function. The most obvious way to do this uses *args and **kwargs.

def identity_decorator(wrapped):
    def wrapper(*args, **kwargs):
        return wrapped(*args, **kwargs)
    return wrapper

@identity_decorator
def foo(a):
    pass

This, not surprisingly, produces a function with an argument spec reflecting the *args and **kwargs.

>>> inspect.getargspec(foo)
ArgSpec(args=[], varargs='args', keywords='kwargs', defaults=None)

Is there a way to either change the argument spec to match the wrapped function or create the function with the right argument spec initially?

like image 606
Gregory Nisbet Avatar asked Oct 07 '16 22:10

Gregory Nisbet


People also ask

Can Python decorator take argument?

The decorator arguments are accessible to the inner decorator through a closure, exactly like how the wrapped() inner function can access f . And since closures extend to all the levels of inner functions, arg is also accessible from within wrapped() if necessary.

What is the biggest advantage of the decorator in Python?

A decorator in Python is a function that takes another function as its argument, and returns yet another function . Decorators can be extremely useful as they allow the extension of an existing function, without any modification to the original function source code.

Is decorator can be chained?

Chaining decorators means applying more than one decorator inside a function. Python allows us to implement more than one decorator to a function. It makes decorators useful for reusable building blocks as it accumulates several effects together. It is also known as nested decorators in Python.


2 Answers

AFAIK, it is possible only since Python 3.3 with the Signature object:

def identity_decorator(wrapped):
    def wrapper(*args, **kwargs):
        return wrapped(*args, **kwargs)
    wrapper.__signature__ = inspect.signature(wrapped)  # the magic is here!
    return wrapper

Then, you can do:

@identity_decorator
def foo(a):
    pass

and finally:

>>> inspect.getargspec(foo)
ArgSpec(args=['a'], varargs=None, keywords=None, defaults=None)
like image 67
Serge Ballesta Avatar answered Oct 08 '22 00:10

Serge Ballesta


As suggested in comments, you can use the decorator module or you can use eval evil powers to create a lambda function with correct signature:

import inspect

def identity_decorator(wrapped):
    argspec = inspect.getargspec(wrapped)
    args = inspect.formatargspec(*argspec)

    def wrapper(*args, **kwargs):

        return wrapped(*args, **kwargs)

    func = eval('lambda %s: wrapper%s' % (args.strip('()'), args), locals())

    return func

@identity_decorator
def foo(a):
    pass

This is kinda hackish, but it preserves function arguments:

>>> inspect.getargspec(foo)
ArgSpec(args=['a'], varargs=None, keywords=None, defaults=None)
like image 27
Guillaume Avatar answered Oct 07 '22 22:10

Guillaume