This recipe provides a descriptor and a decorator. The decorator will be used on any method in your class to indicate that you want that method to inherit its docstring.
This is useful when you are using abstract bases classes and want a method to have the same docstring as the abstract method it implements.
This recipe uses recipe #577745, the deferred_binder module.
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
"""docfunc module""" from deferred_binder import DeferredBinder class DocFunc(DeferredBinder): TRIGGER = None def __init__(self, f): super().__init__(f.__name__, f) self.f = self.target @staticmethod def transform(name, context, target, obj=None): """The DeferredBinder transform for this subclass. name - the attribute name to which the function will be bound. context - the class/namespace to which the function will be bound. target - the function that will be bound. obj - ignored. The DeferredBinder descriptor class will replace itself with the result of this method, when the name to which the descriptor is requested for the first time. This can be on the class or an instances of the class. This way the class to which the method is bound is available so that the inherited docstring can be identified and set. """ namespace, cls = context doc = target.__doc__ if doc == DocFunc.TRIGGER: doc = DocFunc.get_doc(cls, name, DocFunc.TRIGGER) target.__doc__ = doc return target @staticmethod def get_doc(cls, fname, default=TRIGGER, member=True): """Returns the function docstring the method should inherit. cls - the class from which to start looking for the method. fname - the method name on that class default - the docstring to return if none is found. member - is the target function already bound to cls? """ print(cls) bases = cls.__mro__[:] if member: bases = bases[1:] for base in bases: print(base) func = getattr(base, fname, None) if not func: continue doc = getattr(func, '__doc__', default) if doc == default: continue return doc return default @staticmethod def inherits_docstring(f, context=None, fname=None, default=TRIGGER): """A decorator that returns a new DocFunc object. f - the function to decorate. context - the class/namespace where the function is bound, if known. fname - the function name in that context, if known. default - the docstring to return if none is found. """ if context is not None: cls, namespace = context fname = fname or f.__name__ f.__doc__ = DocFunc.get_doc(cls, fname, default, False) return f return DocFunc(f, default)
from docfunc import DocFunc
class Person(metaclass=ABCMeta): @abstractmethod def welcome(self, me, salutation): """Welcome the person into your location. me - the Person object that called welcome(). salutation - what I say. Returns: the person's response to your welcome. """ class RunningAway(Eception): pass class Dad(Person): WARNINGS = ( "I need ", "Don't get mad, but ", ) @DocFunc.inherits_docstring def welcome(self, me, salutation): if isinstance(me, TeenageSon): for warning in self.WARNINGS: if salutation.startswith(warning): raise RunningAway() class TeenageSon(Person): WARNINGS = ( "Why haven't you ", "When are you going to ", ) @DocFunc.inherits_docstring def welcome(self, me, salutation): if isinstance(me, Dad): for warning in self.WARNINGS: if salutation.startswith(warning): raise RunningAway() assert TeenageSon.welcome.__doc__ == Person.welcome.__doc__
You may have noticed that the decorator can be used a little more generally. Here's how to use it to decorate non-methods.
def inherits_docstring(cls, fname): context = (cls.__dict__, cls) def decorator(f): return DocFunc.inherits_docstring(f, context, fname) return decorator class Mover: def go(self, target): """Go to the target.""" @inherits_docstring(Mover, "go") def run(self, target): raise NotImplementedError("But he's only a baby!") #from family import Baby #Baby.run = run
Metaclass to have all your classes inherit docstrings when the current docstring is the empty string.
class EmptyDocFunc(DocFunc): TRIGGER = '' class Meta(type): def __init__(cls, name, bases, namespace): doc = namespace.get("__doc__"): if doc == '': for base in cls.__mro__[1:]: if base.__doc__: doc = base.__doc__ break cls.__doc__ = doc for fname, f in namespace.items(): if not isinstance(f, FunctionType): continue setattr(cls, fname, EmptyDocFunc.inherits_docstring(f))
One drawback of this approach is that the docstring is only assigned after the class body has been executed, and therefore not accessible for any other decorators. I've come up with a different method that avoids this problem, but it doesn't interact quite as nicely with abstract base classes: http://code.activestate.com/recipes/578587-inherit-method-docstrings-without-breaking-decorat/