Applying functools.wraps to nested wrappers
<p>I have a base decorator that takes arguments but that also is built upon by other decorators. I can't seem to figure where to put the functools.wraps in order to preserve the full signature of the decorated function.</p>

<pre><code>import inspect
from functools import wraps

# Base decorator
def _process_arguments(func, *indices):
    """ Apply the pre-processing function to each selected parameter """
    @wraps(func)
    def wrap(f):
        @wraps(f)
        def wrapped_f(*args):
            params = inspect.getargspec(f)[0]

            args_out = list()
            for ind, arg in enumerate(args):
                if ind in indices:
                    args_out.append(func(arg))
                else:
                    args_out.append(arg)

            return f(*args_out)
        return wrapped_f
    return wrap


# Function that will be used to process each parameter
def double(x):
    return x * 2

# Decorator called by end user
def double_selected(*args):
    return _process_arguments(double, *args)

# End-user's function
@double_selected(2, 0)
def say_hello(a1, a2, a3):
    """ doc string for say_hello """
    print('{} {} {}'.format(a1, a2, a3))

say_hello('say', 'hello', 'arguments')
</code></pre>

<p>The result of this code should be <em>and is</em>:</p>

<pre><code>saysay hello argumentsarguments
</code></pre>

<p>However, running help on say_hello gives me:</p>

<pre><code>say_hello(*args, **kwargs)
    doc string for say_hello
</code></pre>

<p>Everything is preserved except the parameter names.</p>

<p>It seems like I just need to add another @wraps() somewhere, but where?</p>

<p>I experimented with this: </p>

<pre><code>&gt;&gt;&gt; from functools import wraps
&gt;&gt;&gt; def x(): print(1)
...
&gt;&gt;&gt; @wraps(x)
... def xyz(a,b,c): return x


&gt;&gt;&gt; xyz.__name__
'x'
&gt;&gt;&gt; help(xyz)
Help on function x in module __main__:

x(a, b, c)
</code></pre>

<p>AFAIK, this has nothing to do with <code>wraps</code> itself, but an issue related to <code>help</code>. Indeed, because <code>help</code> inspects your objects to provide the information, including <code>__doc__</code> and other attributes, this is why you get this behavior, although your wrapped function has different argument list. Though, <code>wraps</code> doesn't update that automatically (the argument list) what it really updates is this tuple and the <code>__dict__</code> which is technically the objects namespace: </p>

<pre><code>WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                       '__annotations__')
WRAPPER_UPDATES = ('__dict__',)
</code></pre>

<p>If you aren't sure about how <code>wraps</code> work, probably it'll help if your read the the source code from the standard library: <code>functools.py</code>.</p>

<blockquote>
  <p>It seems like I just need to add another @wraps() somewhere, but where?</p>
</blockquote>

<p>No, you don't need to add another <code>wraps</code> in your code, <code>help</code> as I stated above works that way by inspecting your objects. The function's arguments  are associated with code objects (<code>__code__</code>) because your function's arguments are stored/represented in that object, <code>wraps</code> has no way to update the argument of the wrapper to be like the wrapped function (continuing with the above example): </p>

<pre><code>&gt;&gt;&gt; xyz.__code__.co_varnames

&gt;&gt;&gt; xyz.__code__.co_varnames = x.__code__.co_varnames
AttributeError: readonly attribute
</code></pre>

<p>If <code>help</code> displayed that function <code>xyz</code> has this argument list <code>()</code> instead of <code>(a, b, c)</code> then this is clearly wrong! And the same applies for <code>wraps</code>, to change the  argument list of the wrapper to the wrapped, would be cumbersome! So this should not be a concern at all. </p>

<pre><code>&gt;&gt;&gt; @wraps(x, ("__code__",))
... def xyz(a,b,c): pass
...

&gt;&gt;&gt; help(xyz)
Help on function xyz in module __main__:

xyz()
</code></pre>

<p>But <code>xyz()</code> returns <code>x()</code>: </p>

<pre><code>&gt;&gt;&gt; xyz()
1
</code></pre>

<p>For other references take a look at this question or the Python Documentation</p>

<p><a href="http://stackoverflow.com/questions/308999/what-does-functools-wraps-do">What does functools.wraps do?</a></p>

python python-2.7 python-decorators functools