可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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
.
回答1:
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'
回答2:
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.
回答3:
The simplest way is to assign it as a class variable:
class ParentWithDocstring(object):
"""Parent docstring"""
pass
class SubClassWithoutDoctring(ParentWithDocstring):
__doc__ = ParentWithDocstring.__doc__
parent = ParentWithDocstring()
print parent.__doc__ # Prints "Parent docstring".
subclass = SubClassWithoutDoctring()
assert subclass.__doc__ == parent.__doc__
It's manual, unfortunately, but straightforward. Incidentally, while string formatting doesn't work the usual way, it does with the same method:
class A(object):
_validTypes = (str, int)
__doc__ = """A accepts the following types: %s""" % str(_validTypes)
A accepts the following types: (<type 'str'>, <type 'int'>)
回答4:
You can also do it using @property
class ParentWithDocstring(object):
"""Parent docstring"""
pass
class SubClassWithoutDocstring(ParentWithDocstring):
@property
def __doc__(self):
return None
class SubClassWithCustomDocstring(ParentWithDocstring):
def __init__(self, docstring, *args, **kwargs):
super(SubClassWithCustomDocstring, self).__init__(*args, **kwargs)
self.docstring = docstring
@property
def __doc__(self):
return self.docstring
>>> parent = ParentWithDocstring()
>>> print parent.__doc__ # Prints "Parent docstring".
Parent docstring
>>> subclass = SubClassWithoutDocstring()
>>> print subclass.__doc__ # Prints "None"
None
>>> subclass = SubClassWithCustomDocstring('foobar')
>>> print subclass.__doc__ # Prints "foobar"
foobar
You can even overwrite a docstring.
class SubClassOverwriteDocstring(ParentWithDocstring):
"""Original docstring"""
def __init__(self, docstring, *args, **kwargs):
super(SubClassOverwriteDocstring, self).__init__(*args, **kwargs)
self.docstring = docstring
@property
def __doc__(self):
return self.docstring
>>> subclass = SubClassOverwriteDocstring('new docstring')
>>> print subclass.__doc__ # Prints "new docstring"
new docstring
One caveat, the property can't be inherited by other classes evidently, you have to add the property in each class that you want to overwrite the docstring.
class SubClassBrokenDocstring(SubClassOverwriteDocstring):
"""Broken docstring"""
def __init__(self, docstring, *args, **kwargs):
super(SubClassBrokenDocstring, self).__init__(docstring, *args, **kwargs)
>>> subclass = SubClassBrokenDocstring("doesn't work")
>>> print subclass.__doc__ # Prints "Broken docstring"
Broken docstring
Bummer! But definitely easier than doing the meta class thing!