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?
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.
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.
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.
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)
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With