I think the metaclass' __prepare__
method can be used for this by injecting a decorator that knows about the class hierarchy:
def log_docstring(fn):
print('docstring for %r is %r' % (fn, fn.__doc__))
return fn
class InheritableDocstrings(type):
def __prepare__(name, bases):
classdict = dict()
# Construct temporary dummy class to figure out MRO
mro = type('K', bases, {}).__mro__[1:]
assert mro[-1] == object
mro = mro[:-1]
def inherit_docstring(fn):
if fn.__doc__ is not None:
raise RuntimeError('Function already has docstring')
# Search for docstring in superclass
for cls in mro:
super_fn = getattr(cls, fn.__name__, None)
if super_fn is None:
continue
fn.__doc__ = super_fn.__doc__
break
else:
raise RuntimeError("Can't inherit docstring for %s: method does not "
"exist in superclass" % fn.__name__)
return fn
classdict['inherit_docstring'] = inherit_docstring
return classdict
class Animal():
def move_to(self, dest):
'''Move to *dest*'''
pass
class Bird(Animal, metaclass=InheritableDocstrings):
@log_docstring
@inherit_docstring
def move_to(self, dest):
self._fly_to(dest)
assert Animal.move_to.__doc__ == Bird.move_to.__doc__
Prints:
docstring for <function Bird.move_to at 0x7f6286b9a200> is 'Move to *dest*'
Of course, this approach has some other issues:
- Some analysis tools (e.g. pyflakes) will complain about the use of the (apparently) undefined inherit_docstring
name
- It doesn't work if the parent class already has a different metaclass (e.g. ABCMeta
).