I have an attribute wrapper class along these lines:
import functools
class wrapper(object):
def __init__(self, function):
self.function = function
functools.update_wrapper(self, function)
def __call__(self, *args, **kwargs):
# Do some stuff here first
return self.function(self.obj, *args, **kwargs)
def __get__(self, instance, owner):
self.cls = owner
self.obj = instance
return self.__call__
And then I can wrap some method Bar.foo:
class Bar:
@wrapper
def foo(self):
""" This is the docstring I want to see """
pass
Now, if I create some instance of Bar, and look at Bar.foo, it is obviously a wrapper, and it does not have a docstring. I'd like to somehow patch things so that the original docstring from the wrapped function shows up (even better, I'd like to modify it slightly to include the fact that it is wrapped). Is it possible to do this?
It turns out, that decorators in form of class do not work in the same way as "usual" decorators (in form of functions). update_wrapper updates attributes of decorator instance, but when you apply that decorator to a method of a class, __doc__ and other attributes actually will be taken from __call__ method of the decorator's class (because you return __call__ from __get__). Example:
import functools
class wrapper1(object):
"""This is the wrapper1 class"""
def __init__(self, wrapped):
"""This is the wrapper1.__init__"""
self.wrapped = wrapped
functools.update_wrapper(self, wrapped)
def __call__(self, *args, **kwargs):
"""This is the wrapper1.__call__"""
print "__call__ of wrapper1.__call__"
if hasattr(self, 'obj'):
return self.wrapped(self.obj, *args, **kwargs)
return self.wrapped(*args, **kwargs)
def __get__(self, instance, owner):
self.cls = owner
self.obj = instance
return self.__call__
def wrapper2(wrapped):
@functools.wraps(wrapped)
def _wrapper(*args, **kwargs):
print "_wrapper of wrapper2 decorator"
return wrapped(*args, **kwargs)
return _wrapper
@wrapper1
def f1():
"""The f1 function"""
print "f1 call"
@wrapper2
def f2():
"""The f2 function"""
print "f2 call"
print "call of f1: '{}'".format(f1.__doc__)
f1()
print "call of f2: '{}'".format(f2.__doc__)
f2()
class A(object):
def __init__(self, desc):
self.desc = desc
@wrapper1
def m1(self):
"""The A.m1 method"""
print "A.m1 call: {}".format(self.desc)
@wrapper2
def m2(self):
"""The A.m2 method"""
print "A.m2 call: {}".format(self.desc)
a = A('Hello!')
print "Call of A.m2: {}: {}".format(a.m2.__doc__, A.m2.__doc__)
a.m2()
print "Call of A.m1: {}: {}".format(a.m1.__doc__, A.m1.__doc__)
a.m1()
Last two lines print out:
Call of A.m1: This is the wrapper1.__call__: This is the wrapper1.__call__
__call__ of wrapper1.__call__
A.m1 call: Hello!
If the form of decorators does not really matter, then use decorators as def wrapper(wrapped):. It is simpler and clearer.
But if you still want to use class form of decorator for some reasons, then change __get__ method to return self, it will fix undesired behaviour:
def __get__(self, instance, owner):
self.cls = owner
self.obj = instance
return self
As Aleksandr pointed in his answer __doc__ will be taken from __call__ method of wrapper class
Documentation states, that __doc__ is a read-only attribute of user-defined methods, but function's __doc__ isn't.
So call functools.update_wrapper on self.__call__.__func__ instead of self inside __init__ method.
If you want to modify the original docstring then you may do it with following code
self.__call__.__func__.__doc__ = self.__call__.__doc__ + ' added later'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With