14

在 Python 类继承中有一个关于Inherit docstrings 的问题,但那里的答案涉及方法文档字符串。

我的问题是如何继承父类的文档字符串作为__doc__属性。用例是Django rest 框架根据您的视图类的文档字符串在您的 API 的 html 版本中生成很好的文档。但是在没有文档字符串的类中继承基类(带有文档字符串)时,API 不会显示文档字符串。

很可能是 sphinx 和其他工具做了正确的事情并为我处理了文档字符串继承,但是 django rest 框架会查看 (empty).__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.

4

4 回答 4

14

由于您不能__doc__为类分配新的文档字符串(至少在 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__in设置__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

然后至少您的实例有一个文档字符串:

>>> 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(修复问题 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'
于 2012-12-18T16:40:16.987 回答
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方法被认为是私有的,并且将来可能会更改,因此您需要在升级时密切关注发行说明,以了解那里的任何更改。

于 2012-12-18T17:49:01.537 回答
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'>)
于 2013-07-25T16:02:15.680 回答
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

无赖!但绝对比做元类的事情容易!

于 2014-01-17T01:03:04.907 回答