Returning modified docstring from attribute wrapping method
I have an attribute wrapper class on the following 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 Bar.foo method:
class Bar:
@wrapper
def foo(self):
""" This is the docstring I want to see """
pass
Now if I create some kind of Bar instance and look at Bar.foo it is obviously a wrapper and it doesn't have a docstring. I would like to somehow catch everything so that the original docstring from the wrapped function appears (even better, I would modify it a bit to include the fact that it is wrapped). Can this be done?
source to share
It turns out that class-form decorators work differently than "regular" decorators (as functions). update_wrapper
updates the attributes of the decorator instance, but when you apply that decorator to a class method, the __doc__
other attributes will actually be taken from the __call__
decorator class method (because you are returning __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()
The last two lines are printed:
Call of A.m1: This is the wrapper1.__call__: This is the wrapper1.__call__
__call__ of wrapper1.__call__
A.m1 call: Hello!
If the shape of the decorators really doesn't matter, use decorators like def wrapper(wrapped):
. It's simpler and clearer. But if you still want to use the cool form of the decorator for some reason, change the method __get__
to return self
, it will fix the unwanted behavior:
def __get__(self, instance, owner):
self.cls = owner
self.obj = instance
return self
source to share
As Alexander pointed out in his answer __doc__
will be taken from __call__
the wrapper
class method
The documentation claims __doc__
to be a read-only attribute of custom methods, but a function is __doc__
not.
So call functools.update_wrapper
on self.__call__.__func__
instead self
inside the method __init__
.
If you want to change the original docstring you can do it with the following code
self.__call__.__func__.__doc__ = self.__call__.__doc__ + ' added later'
source to share