Welcome, guest | Sign In | My Account | Store | Cart

You'll find three different approaches to copying the method's docstring to the overriding method on a child class.

The function decorator approach is limited by the fact that you have to know the class when you call the decorator, so it can't be used inside a class body. However, recipe #577746 provides a function decorator that does not have this limitation.

Python, 115 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""inherits_docstring module"""

def get_docstring(bases, name):
    for base in bases:
        if name not in base.__dict__:
            continue
        attr = getattr(base, name)
        if not hasattr(attr, "__doc__"):
            continue
        if attr.__doc__ is None:
            continue
        return attr.__doc__
    return None


def inherits_docstring_func(f, cls=None, funcname=None):
    """A function decorator for inheriting function docstrings.

    Look to cls for the docstring to inherit.  The funcname argument
    indicates that the cls should be searched for that name instead
    of f.__name__.

    """

    name = funcname
    if not name:
        name = f.__name__
    if cls is None:
        """shouldn't get here"""

    # go for it

    if funcname:
        docstring = get_docstring((cls,), name)
    else:
        docstring = None

    if docstring is None:
        docstring = get_docstring(cls.__bases__, name)
    if docstring is None and hasattr(cls, "__implements__"):
        # for registrations on ABCs
        docstring = get_docstring(cls.__implements__, name)

    if docstring is not None:
        f.__doc__ = docstring
    return f


def inherits_docstring(f, cls=None, funcname=None):
    """A decorator for inheriting function docstrings.

    If f is a class, return a decorator that looks to the class for
    the docstring to inherit.

    """
    if isinstance(f, type):
        #passed a class, so return a decorator
        cls = f
        def decorator(func):
            return inherits_docstring(func, cls)
        return decorator

    # otherwise used as decorator
    if cls is None:
        # don't know how to do this without a metaclass
        raise NotImplementedError
    return inherits_docstring_func(f, cls, funcname)


class DocDeco:
    """A class decorator tool for inheriting method docstrings.

    """

    class MethodWrapper:
        """decorator"""
        def __init__(self, f):
            self.f = f
    inherits_docstring = MethodWrapper

    def helps_docstrings(cls):
        """The class decorator."""

        for name, obj in cls.__dict__.items():
            if not isinstance(obj, DocDeco.MethodWrapper):
                # only act on decorated methods
                continue

            f = inherits_docstring(obj.f, cls)
            setattr(cls, name, f)

        return cls


class DocMeta(type):
    """A metaclass for inheriting method docstrings.

    """

    class MethodWrapper:
        """decorator"""
        def __init__(self, f):
            self.f = f
    inherits_docstring = MethodWrapper

    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)

        for attr, obj in namespace.items():
            if not isinstance(obj, cls.MethodWrapper):
                # only act on decorated methods
                continue

            f = inherits_docstring(obj.f, cls)
            setattr(cls, attr, f)

Example 1

Using the plain function decorator after the class definition.

class Parent:
    def f(self):
        "something"

class Child(Parent):
    def f(self): pass
Child.f = inherits_docstring(Child.f, Child)

assert Child.f.__doc__ == "something"
print("new docstring:", Child.f.__doc__)

Example 2

Using the class decorator.

class A:
    def f(self):
        "something"

@DocDeco.helps_docstrings
class B(A):
    @DocDeco.inherits_docstring
    def f(self): pass

assert B.f.__doc__ == "something"
print("new docstring:", B.f.__doc__)

Example 3

Using the metaclass.

print("Example 3")

class X(metaclass=DocMeta):
    def f(self):
        "something"

class Y(X):
    @DocMeta.inherits_docstring
    def f(self): pass

assert Y.f.__doc__ == "something"
print("new docstring:", Y.f.__doc__)

Alternative

You could use my modulehacker recipe (recipe 577740) to have all methods of each class inherit their docstring, if missing.

import types
import modulehacker

class DocGiver(modulehacker.Hacker):
    def fix_classdoc(self, cls):
        if cls.__doc__:
            return
        for base in cls.__mro__[1:]:
            if base.__doc__:
                cls.__doc__ = base.__doc__
                break

    def fix_methoddoc(self, cls):
        for attr in cls.__dict__:
            method = getattr(cls, attr)
            if not isinstance(method, types.FunctionType):
                continue
            if method.__doc__:
                continue
            for base in cls.__mro__[1:]:
                if attr not in base.__dict__:
                    # not directly in that class
                    continue
                m = getattr(base, attr)
                if isinstance(m, types.FunctionType) and m.__doc__:
                    method.__doc__ = m.__doc__
                break

    def hack(self, module):
        for attr in module.__dict__:
            cls = getattr(module, attr)
            if isinstance(cls, type):
                #self.fix_classdoc(cls)
                self.fix_methoddoc(cls)
        return module

modulehacker.register(DocGiver())

<somemodule.py>

class X:
    "Do some cool stuff"

class Y(X):
    def do_it(self):
        """Actually do the stuff."""

class Z(Y):
    def do_it(self):
        pass

<__main__>

import docgiver
import somemodule

print(somemodule.Z.__doc__)
# Do some cool stuff
print(somemodule.Z.do_it.__doc__)
# Actually do the stuff.