Наследовать родительский класс docstring как атрибут __doc__
Возникает вопрос о Наследовать docstrings в наследовании класса Python, но ответы там касаются метода docstrings.
Мой вопрос заключается в том, как наследовать docstring родительского класса как атрибут __doc__
. Usecase заключается в том, что Django rest framework создает хорошую документацию в html-версии вашего API на основе docstrings ваших классов. Но при наследовании базового класса (с docstring) в классе без docstring API не показывает docstring.
Вполне возможно, что sphinx и другие инструменты делают правильные вещи и обрабатывают наследование docstring для меня, но django rest framework смотрит на (пустой) атрибут .__doc__
.
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"
Я пробовал что-то вроде super(SubClassWithoutDocstring, self).__doc__
, но это также дало мне None
.
Ответы
Ответ 1
Поскольку вы не можете назначить новую __doc__
docstring для класса (по крайней мере, в CPython), вам придется использовать метакласс:
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)
Да, мы перепрыгиваем через дополнительный обруч или два, но вышеупомянутый метакласс найдет правильный __doc__
, но запутанный, вы делаете свой график наследования.
Использование:
>>> class ParentWithDocstring(object):
... """Parent docstring"""
...
>>> class SubClassWithoutDocstring(ParentWithDocstring):
... __metaclass__ = inheritdocstring
...
>>> SubClassWithoutDocstring.__doc__
'Parent docstring'
Альтернативой является установка __doc__
в __init__
в качестве переменной экземпляра:
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
Тогда, по крайней мере, ваши экземпляры имеют 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'
Как и в случае с Python 3.3 (который исправил issue 12773), вы можете, наконец, просто установить атрибут __doc__
для пользовательских классов, так что тогда вы вместо этого можно использовать декоратор класса:
import inspect
def inheritdocstring(cls):
for base in inspect.getmro(cls):
if base.__doc__ is not None:
cls.__doc__ = base.__doc__
break
return cls
который затем может быть применен таким образом:
>>> @inheritdocstring
... class SubClassWithoutDocstring(ParentWithDocstring):
... pass
...
>>> SubClassWithoutDocstring.__doc__
'Parent docstring'
Ответ 2
В этом конкретном случае вы также можете переопределить, как среда REST определяет имя, используемое для конечной точки, путем переопределения метода .get_name()
.
Если вы берете этот маршрут, вы, вероятно, обнаружите, что хотите определить набор базовых классов для своих представлений и переопределите метод во всех своих базовых представлениях с помощью простого класса mixin.
Например:
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
Обратите внимание также, что метод get_name
считается закрытым и, вероятно, изменится в какой-то момент в будущем, поэтому вам нужно будет следить за примечаниями к выпуску при обновлении для любых изменений там.
Ответ 3
Самый простой способ - назначить его как переменную класса:
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__
Это руководство, к сожалению, но прямолинейное. Кстати, в то время как форматирование строк не работает обычным способом, оно работает по тому же методу:
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
Вы также можете сделать это, используя @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
Вы даже можете перезаписать 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
Одно из предостережений, свойство не может быть унаследовано другими классами, очевидно, вам нужно добавить свойство в каждый класс, который вы хотите перезаписать 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
Облом! Но определенно проще, чем делать мета-класс!