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.
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.