Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inherit a parent class docstring as __doc__ attribute

There is a question about Inherit docstrings in Python class inheritance, but the answers there deal with method docstrings.

My question is how to inherit a docstring of a parent class as the __doc__ attribute. The usecase is that Django rest framework generates nice documentation in the html version of your API based on your view classes' docstrings. But when inheriting a base class (with a docstring) in a class without a docstring, the API doesn't show the docstring.

It might very well be that sphinx and other tools do the right thing and handle the docstring inheritance for me, but django rest framework looks at the (empty) .__doc__ attribute.

class ParentWithDocstring(object):
    """Parent docstring"""
    pass


class SubClassWithoutDoctring(ParentWithDocstring):
    pass


parent = ParentWithDocstring()
print parent.__doc__  # Prints "Parent docstring".
subclass = SubClassWithoutDoctring()
print subclass.__doc__  # Prints "None"

I've tried something like super(SubClassWithoutDocstring, self).__doc__, but that also only got me a None.

like image 477
Reinout van Rees Avatar asked Dec 18 '12 16:12

Reinout van Rees


People also ask

What does __ doc __ mean in Python?

The __doc__ attribute Each Python object (functions, classes, variables,...) provides (if programmer has filled it) a short documentation which describes its features. You can access it with commands like print myobject.

Should __ init __ have a docstring?

Public methods (including the __init__ constructor) should also have docstrings. A package may be documented in the module docstring of the __init__.py file in the package directory. String literals occurring elsewhere in Python code may also act as documentation.

What is docstring in Python examples?

A Python docstring is a string used to document a Python module, class, function or method, so programmers can understand what it does without having to read the details of the implementation. Also, it is a common practice to generate online (html) documentation automatically from docstrings.


2 Answers

Since you cannot assign a new __doc__ docstring to a class (in CPython at least), you'll have to use a metaclass:

import inspect

def inheritdocstring(name, bases, attrs):
    if not '__doc__' in attrs:
        # create a temporary 'parent' to (greatly) simplify the MRO search
        temp = type('temporaryclass', bases, {})
        for cls in inspect.getmro(temp):
            if cls.__doc__ is not None:
                attrs['__doc__'] = cls.__doc__
                break

    return type(name, bases, attrs)

Yes, we jump through an extra hoop or two, but the above metaclass will find the correct __doc__ however convoluted you make your inheritance graph.

Usage:

>>> class ParentWithDocstring(object):
...     """Parent docstring"""
... 
>>> class SubClassWithoutDocstring(ParentWithDocstring):
...     __metaclass__ = inheritdocstring
... 
>>> SubClassWithoutDocstring.__doc__
'Parent docstring'

The alternative is to set __doc__ in __init__, as an instance variable:

def __init__(self):
    try:
        self.__doc__ = next(cls.__doc__ for cls in inspect.getmro(type(self)) if cls.__doc__ is not None)
    except StopIteration:
        pass

Then at least your instances have a docstring:

>>> class SubClassWithoutDocstring(ParentWithDocstring):
...     def __init__(self):
...         try:
...             self.__doc__ = next(cls.__doc__ for cls in inspect.getmro(type(self)) if cls.__doc__ is not None)
...         except StopIteration:
...             pass
... 
>>> SubClassWithoutDocstring().__doc__
'Parent docstring'

As of Python 3.3 (which fixed issue 12773), you can finally just set the __doc__ attribute of custom classes, so then you can use a class decorator instead:

import inspect

def inheritdocstring(cls):
    for base in inspect.getmro(cls):
        if base.__doc__ is not None:
            cls.__doc__ = base.__doc__
            break
    return cls

which then can be applied thus:

>>> @inheritdocstring
... class SubClassWithoutDocstring(ParentWithDocstring):
...     pass
... 
>>> SubClassWithoutDocstring.__doc__
'Parent docstring'
like image 100
Martijn Pieters Avatar answered Oct 04 '22 03:10

Martijn Pieters


In this particular case you could also override how REST framework determines the name to use for the endpoint, by overriding the .get_name() method.

If you do take that route you'll probably find yourself wanting to define a set of base classes for your views, and override the method on all your base view using a simple mixin class.

For example:

class GetNameMixin(object):
    def get_name(self):
        # Your docstring-or-ancestor-docstring code here

class ListAPIView(GetNameMixin, generics.ListAPIView):
    pass

class RetrieveAPIView(GetNameMixin, generics.RetrieveAPIView):
    pass

Note also that the get_name method is considered private, and is likely to change at some point in the future, so you would need to keep tabs on the release notes when upgrading, for any changes there.

like image 22
Tom Christie Avatar answered Oct 04 '22 03:10

Tom Christie