10

I use Functools.update_wrapper() in my decorator, but It seems like update_wrapper rewrites only function attributes (such as __doc__, __name__), but does not affect on help() function.

I aware of these answers, but they don't work with decorator-class.

Here is my function.

import functools

class memoized(object):

    def __init__(self, func):
        self.func = func
        functools.update_wrapper(self, func)

    def __call__(self, *args):
        self.func(*args)

@memoized 
def printer(arg):
    "This is my function"
    print arg

Here is the output

>>> printer.__doc__
This is my function

>>> help(printer)
Help on memoized in module __main__ object:

printer = class memoized(__builtin__.object)
 |  Methods defined here:
 |  
 |  __call__(self, *args)
 |  
 |  __init__(self, func)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

It looks like a bug, but how can I fix it?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
prybalko
  • 115
  • 1
  • 7

1 Answers1

15

functools.update_wrapper() sets the attribute on the instance, but in Python versions up to 3.8, help() looks at the information on the type.

So printer.__doc__ gives you the instance attribute, help() prints information about type(printer), e.g. the memoized class, which does not have a __doc__ attribute.

This was not considered a bug, this was all by design; help() will always look at the class when you pass in an instance. Don't use a class as decorator if you want help() to work for the decorated function, or upgrade your Python version.

This was eventually changed in Python 3.9, see the bug report discussion.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    So, there is no way to fix it, as long as I use a class as decorator? – prybalko Sep 22 '14 at 11:58
  • @user3664218: indeed; `help()` gives you information about the class, not the instance, and if you want the latter you should not use a class decorator. – Martijn Pieters Sep 22 '14 at 12:01
  • This means that class decorators are partly broken. What's the justification for this design? – Neil G Mar 04 '15 at 03:42
  • 1
    @NeilG the comments in the pydoc source are clear: because normally when you pass in an instance of something you typically want to document the methods available, not the instance attributes. That is the normal usecase. Pydoc cannot detect that here the instance is used as a replacement for a method, and that may be unfortunate but I'm not sure that you'd call it *broken*. – Martijn Pieters Mar 04 '15 at 07:44
  • 4
    I think it's broken because using a class is a standard way and I the most versatile way to implement decorators. One workaround that they should have considered would have been to have a `__helpobject__` magic method that defaults to `type(self) if not isinstance(self, type) else self`, which would allow classes that implement a decorator pattern to override this method. – Neil G Mar 04 '15 at 08:08
  • 1
    @NeilG: Using a class to decorate is not all *that* standard; most decorators use functions (with a closure). I'd only use a class if I needed to implement custom binding behaviour or additional methods on the object. Decorators are a relatively late addition to Python, Pydoc is an older setup. If this issue is something enough people care about the core developers probably will come up with something. You can help by discussion ideas on Python-ideas or filing an issue on bugs.python.org. – Martijn Pieters Mar 04 '15 at 08:11
  • Thanks, you're right. For me, it's too small a wart for me to worry about posting it on ideas. I don't think it belongs in bugs. Come to think of it, if I've decorated something, I probably want help from the whole decoration chain, so I want to get the decorator's doc and the function's doc, and so on. functools.wraps was always kind of ugly. – Neil G Mar 04 '15 at 08:21
  • @MartijnPieters you refer to `functools.wrapper()`. You mean `functools.update_wrapper()` correct? – pylang Dec 07 '17 at 05:00
  • @pylang: ah, yes, in this case, I did. `functools.wrapper()` is the decorator that applies `functools.update_wrapper()`. – Martijn Pieters Dec 07 '17 at 07:45