有一个关于Inherit docstrings in Python class inheritance的问题,但那里的答案涉及方法文档字符串。
我的问题是如何继承父类的docstring作为__doc__
属性。用例是Django rest framework根据您的视图类的文档字符串在API的html版本中生成了很好的文档。但是当在没有docstring的类中继承基类(带有docstring)时,API不会显示文档字符串。
很可能sphinx和其他工具做正确的事情并为我处理docstring继承,但是django rest框架会查看(空).__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
。
答案 0 :(得分:12)
由于您无法为类(至少在CPython中)分配新的__doc__
文档字符串,因此您必须使用元类:
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'
答案 1 :(得分:2)
在这种特殊情况下,您还可以通过覆盖.get_name()
方法来覆盖REST框架如何确定要用于端点的名称。
如果您确实采用了这种方法,您可能会发现自己想要为视图定义一组基类,并使用简单的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
方法被视为私有方式,并且可能在将来的某个时间点发生更改,因此您需要在升级时密切关注发行说明,以及其中的任何更改。
答案 2 :(得分:2)
最简单的方法是将其指定为类变量:
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'>)
答案 3 :(得分:0)
您也可以使用@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
您甚至可以覆盖文档字符串。
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
有一点需要注意,该属性显然不能被其他类继承,您必须在要覆盖文档字符串的每个类中添加该属性。
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
无赖!但绝对比做meta类更容易!