Suppose I have written a decorator that does something very generic. For example, it might convert all arguments to a specific type, perform logging, implement memoization, etc.
Here is an example:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
>>> funny_function("3", 4.0, z="5")
22
Everything well so far. There is one problem, however. The decorated function does not retain the documentation of the original function:
>>> help(funny_function)
Help on function g in module __main__:
g(*args, **kwargs)
Fortunately, there is a workaround:
def args_as_ints(f):
def g(*args, **kwargs):
args = [int(x) for x in args]
kwargs = dict((k, int(v)) for k, v in kwargs.items())
return f(*args, **kwargs)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
@args_as_ints
def funny_function(x, y, z=3):
"""Computes x*y + 2*z"""
return x*y + 2*z
This time, the function name and documentation are correct:
>>> help(funny_function)
Help on function funny_function in module __main__:
funny_function(*args, **kwargs)
Computes x*y + 2*z
But there is still a problem: the function signature is wrong. The information "*args, **kwargs" is next to useless.
What to do? I can think of two simple but flawed workarounds:
1 -- Include the correct signature in the docstring:
def funny_function(x, y, z=3):
"""funny_function(x, y, z=3) -- computes x*y + 2*z"""
return x*y + 2*z
This is bad because of the duplication. The signature will still not be shown properly in automatically generated documentation. It's easy to update the function and forget about changing the docstring, or to make a typo. [And yes, I'm aware of the fact that the docstring already duplicates the function body. Please ignore this; funny_function is just a random example.]
2 -- Not use a decorator, or use a special-purpose decorator for every specific signature:
def funny_functions_decorator(f):
def g(x, y, z=3):
return f(int(x), int(y), z=int(z))
g.__name__ = f.__name__
g.__doc__ = f.__doc__
return g
This works fine for a set of functions that have identical signature, but it's useless in general. As I said in the beginning, I want to be able to use decorators entirely generically.
I'm looking for a solution that is fully general, and automatic.
So the question is: is there a way to edit the decorated function signature after it has been created?
Otherwise, can I write a decorator that extracts the function signature and uses that information instead of "*kwargs, **kwargs" when constructing the decorated function? How do I extract that information? How should I construct the decorated function -- with exec?
Any other approaches?
There is a decorator module with
decorator
decorator you can use:Then the signature and help of the method is preserved:
EDIT: J. F. Sebastian pointed out that I didn't modify
args_as_ints
function -- it is fixed now.Take a look at the decorator module - specifically the decorator decorator, which solves this problem.
Install decorator module:
Adapt definition of
args_as_ints()
:Python 3.4+
functools.wraps()
from stdlib preserves signatures since Python 3.4:functools.wraps()
is available at least since Python 2.5 but it does not preserve the signature there:Notice:
*args, **kwargs
instead ofx, y, z=3
.Second option:
$ easy_install wrapt
wrapt have a bonus, preserve class signature.
This is solved with Python's standard library
functools
and specificallyfunctools.wraps
function, which is designed to "update a wrapper function to look like the wrapped function". It's behaviour depends on Python version, however, as shown below. Applied to the example from the question, the code would look like:When executed in Python 3, this would produce the following:
Its only drawback is that in Python 2 however, it doesn't update function's argument list. When executed in Python 2, it will produce: