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?

+3


source to share


2 answers


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

      

+1


source


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'

      

+1


source







All Articles