65

I have a problem using docstrings with decorators. Given the following example:

def decorator(f):
    def _decorator():
        print 'decorator active'
        f()
    return _decorator

@decorator
def foo():
    '''the magic foo function'''
    print 'this is function foo'

help(foo)

Now the help doesn't show me the docstring of foo as expected, it shows:

Help on function _decorator in module __main__:

_decorator()

Without the decorator, the help is correct:

Help on function foo in module __main__:

foo()
    the magic foo function

I know, that the function foo is wrapped by the decorator, and so the function object is not the function foo any more. But what is a nice solution to get the docstring (and the help) as expected?

Günther Jena
  • 3,706
  • 3
  • 34
  • 49

4 Answers4

104

Use functools.wraps() to update the attributes of the decorator:

from functools import wraps

def decorator(f):
    @wraps(f)
    def _decorator():
        print 'decorator active'
        f()
    return _decorator

@decorator
def foo():
    '''the magic foo function'''
    print 'this is function foo'

help(foo)

Also see the Standard Library documentation for functools.

Pär Wieslander
  • 28,374
  • 7
  • 55
  • 54
  • 5
    This doesn't work if `foo` takes any arguments - they get replaced by whatever the `_decorator` uses. This is a problem especially when you want your decorator to take `*args, **kwds`. I've never been able to find a way to get the docstring correct using `functools.wraps`. – Scott Griffiths Nov 23 '09 at 12:53
  • 4
    @Scott Griffiths: The docstring will still be correct even if `foo` takes arguments. However, `help(foo)` will display the parameter list of the `_decorator`, since it actually replaces the `foo` function. There's no good way around this if you're writing decorators that take arbitrary arguments using `*args, **kwargs`, but for me the important point is that the docstring is kept intact. Parameter details could always be specified in the docstring for clarity. – Pär Wieslander Nov 23 '09 at 13:05
  • Thanks for the extra information. I've recently been failing to get the help description correct for decorated functions - it seems a pretty poor state of affairs, but I understand the difficulty as the decorated function could have a completely different signature. Still, there must be a way... :) – Scott Griffiths Nov 23 '09 at 14:27
  • 15
    There is a way to do it. The decorator module http://pypi.python.org/pypi/decorator does it by using a trick. The trick is to rebuild the decorator signature and run exec on it. You can find the trick in line 118 in decorator.py. I think however that this approach is extreme. – Nadia Alramli Nov 23 '09 at 15:11
  • It seems that functools.wraps **does** enable `help()` to work properly now. I'm struggling to find when this changed but I'm still using Python 2.7. Happy day! – John Carrell Nov 30 '17 at 21:24
  • How would I use this method with the docorator `@classmethod`? – MikeyE Jan 31 '18 at 11:48
  • What about putting a line like `_decorator.__doc__ += '\n(decorated by )'` right before `return _decorator`? Seems like that would make the docstring more informative but I haven't seen that so wondering if there is a reason not to do that. – oxer Oct 18 '18 at 20:45
22

I found a solution, but don't know if it's really nice:

def decorator(f):
    def _decorator():
        print 'decorator active'
        f()
    _decorator.__name__=f.__name__
    _decorator.__doc__=f.__doc__
    return _decorator

The part with _decorator.__name__=f.__name__ seems a little bit hideous... What do you think?

Günther Jena
  • 3,706
  • 3
  • 34
  • 49
  • 6
    In fact this is (almost ?) exactly what functools.wraps do :) – thomas Nov 23 '09 at 15:08
  • 2
    It doesn't look hideous to me. It says exactly what you want it to say. "I want the name of this function to be 'myfunction' instead of '_decorator'." – jcdyer Nov 23 '09 at 16:20
  • 2
    you should not re-invent the wheel, especially when there is a working function from standard library which does this, well-tested, maintained and documented – Azat Ibrakov Sep 17 '18 at 10:37
  • 2
    I cannot go along with @Azat Ibrakov; I'd always prefer a clear to read solution which is not dependent on any (also eventually changing) libraries. Therefore, I realy like this simple and straight forward approach (+1). – Markus Jun 03 '19 at 23:25
  • also, this solution doesn't copy the docstrings of the arguments of `f` (if it had any) and return type. `functools.wraps` does this though. – patzm Apr 06 '21 at 08:32
4

Take a look at functools.wraps: http://docs.python.org/library/functools.html

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
-1

The solution is pretty easy. The doc string should be mentioned in the top most decorator that is being called on top of the main function. Find the example below:

import math

def wrap2(func):
    def squarer(x):
        return [math.sqrt(i) for i in func(x)]
    return squarer
    

def wrap1(func):
    def summer(x):
        return [i*2 for i in func(x)]
    return summer

def wrap3(func):
    def rounder(x):
        return [round(i,1) for i in func(x)]
    return rounder

def wrap4(func):
    def stringer(x):
        '''
    Enter the input of a 2-dim array to get the output
    '''
        return [str(i)+' rounds ' for i in func(x)]
    return stringer

@wrap4
@wrap3
@wrap2
@wrap1
def rooter(i):
    return [sum(p) for p in i]

help(rooter)

# output
Help on function stringer in module __main__:

stringer(x)
    Enter the input of a 2-dim array to get the output

---or----

Signature: rooter(x)
Docstring: Enter the input of a 2-dim array to get the output
File:      d:\<ipython-input-392-487fc73b05cf>
Type:      function

So, the Doc String must be mentioned in the wrap4 decorator function for it to be visible in the main function.

Dharman
  • 30,962
  • 25
  • 85
  • 135