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

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.

Python, 81 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
"""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)

Example 1

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__

Example 2

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

Example 3

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

1 comment

nikratio 10 years, 10 months ago  # | flag

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/

Created by Eric Snow on Sat, 11 Jun 2011 (MIT)
Python recipes (4591)
Eric Snow's recipes (39)

Required Modules

  • (none specified)

Other Information and Tasks