Search code examples
pythonpython-decoratorsdocstring

How to automatically document my class properties with decorators


Suppose I have two classes (A & B) of which the second is derived from the first. Further, I hide some of the properties implemented in A by writing new implementations in B. However, the docstring I wrote for A is still valid for B and I'm lazy -- I don't want to copy paste everything.

Please note that a key issue here is my targets are properties, for class methods in general there are solutions posted.

In code, minimal example of classes could look like this:

In [1]: class A(object):
   ...:     @property
   ...:     def a(self):
   ...:         "The a:ness of it"
   ...:         return True
   ...:     

In [2]: class B(A):
   ...:     @property
   ...:     def a(self):
   ...:         return False
   ...:     

What I basically would want is to have a way to make the following happen:

In [8]: B.a.__doc__
Out[8]: 'The a:ness of it'

In reality the docstring of B.a is empty and writing to B.a.__doc__ is impossible as it raises a TypeError.

As far as I have gotten is the following hack of a solution:

from inspect import getmro

def inheritDocFromA(f):
    """This decorator will copy the docstring from ``A`` for the
    matching function ``f`` if such exists and no docstring has been added
    manually.
    """
    if f.__doc__ is None:

        fname = f.__name__

        for c in getmro(A):
            if hasattr(c, fname):
                d = getattr(c, fname).__doc__
                if d is not None:
                    f.__doc__ = d
                    break

    return f

Which does indeed work, but is dead ugly as A is hard-coded into the decorator-function as I have found no way to know what class f is attached to when passed to the decorator:

In [15]: class C(A):
   ....:     @property
   ....:     @inheritDocFromA
   ....:     def a(self):
   ....:         return False
   ....:     

In [16]: C.a.__doc__
Out[16]: 'The a:ness of it'

Question: Is it possible to construct a general solution for decorator applied docstrings on class properties without hard-coding in the inheritance into the decorator function?

I've also tried decorating the classes but then I hit the wall with the properties docstrings being write-protected.

And finally, I'd like the solution to work for both Python 2.7 and 3.4 if possible.


Solution

  • It is possible to write a decorator that returns the proper __doc__ when accessed through class — after all, __get__ receives the type and can go through its MRO, find the appropriate __doc__, and set it on itself (or on a proxy created and returned for that purpose). But it is much simpler to work around the issue of __doc__ not being writable.

    It turns out that since property is implemented as a type, making the __doc__ of its instances writable is as simple as inheriting from it:

    class property_writable_doc(property):
        pass
    

    Then your idea of using a class decorator to inherit the __doc__ of properties can work:

    def inherit_doc_class(cls):
        for name, obj in cls.__dict__.iteritems():
            if isinstance(obj, property_writable_doc) and obj.__doc__ is None:
                for t in cls.__mro__:
                    if name in t.__dict__ and t.__dict__[name].__doc__ is not None:
                        obj.__doc__ = t.__dict__[name].__doc__
                        break
        return cls
    
    class A(object):
        @property
        def a(self):
            "The a:ness of it"
            return True
    
    @inherit_doc_class
    class B(A):
        @property_writable_doc
        def a(self):
            return False
    
    @inherit_doc_class
    class C(A):
        @property_writable_doc
        def a(self):
            "The C:ness of it"
            return False