Reputation: 6994
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?
Upvotes: 11
Views: 1453
Reputation: 148880
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)
Upvotes: 16
Reputation: 10961
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)
Upvotes: 3