Python 将父类docstring作为_doc__属性继承

Python 将父类docstring作为_doc__属性继承,python,docstring,django-rest-framework,Python,Docstring,Django Rest Framework,有一个关于的问题,但答案涉及方法docstring 我的问题是如何继承父类的docstring作为\uuu doc\uu属性。用例是根据视图类的docstring在html版本的API中生成漂亮的文档。但是当在没有docstring的类中继承基类(带有docstring)时,API不会显示docstring class SubClassOverwriteDocstring(ParentWithDocstring): """Original docstring""" def __i

有一个关于的问题,但答案涉及方法docstring

我的问题是如何继承父类的docstring作为
\uuu doc\uu
属性。用例是根据视图类的docstring在html版本的API中生成漂亮的文档。但是当在没有docstring的类中继承基类(带有docstring)时,API不会显示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
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
sphinx和其他工具很可能做了正确的事情,为我处理了docstring继承,但是django rest框架查看了(空)
。\uuuuu doc\uuuu
属性

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(subclass without docstring,self)。\uuuu doc\uuuu
,但这也只能让我得到
None
,因为你不能将一个新的
\uuu doc\uuuu
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)
是的,我们跳过了一个或两个额外的环,但是上面的元类将找到正确的
\uuuu doc\uuuu
,不管您的继承图多么复杂

用法:

>>> class ParentWithDocstring(object):
...     """Parent docstring"""
... 
>>> class SubClassWithoutDocstring(ParentWithDocstring):
...     __metaclass__ = inheritdocstring
... 
>>> SubClassWithoutDocstring.__doc__
'Parent docstring'
另一种方法是在
\uuuuu init\uuuuu
中设置
\uuuu doc\uuuuu
,作为实例变量:

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 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__
那么至少您的实例有一个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'
从Python3.3(已修复)开始,您最终可以设置自定义类的
\uuuu doc\uuuu
属性,这样您就可以使用类装饰器了:

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'

在这种特殊情况下,您还可以通过重写
.get\u 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\u name
方法被认为是私有的,并且在将来的某个时候可能会更改,因此升级时您需要在发行说明上保留标签,以了解其中的任何更改

最简单的方法是将其指定为类变量:

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 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'>)
A类(对象):
_有效类型=(str,int)
__doc_uu=“”A接受以下类型:%s”“%str(_有效类型)
A接受以下类型:(,)

您也可以使用
@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
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
需要注意的是,该属性不能被其他类继承。显然,您必须在要覆盖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
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

真倒霉!但绝对比做元类的事情容易

我只想从我所记得的关于Python的讨论中补充一下。默认情况下,它不会继承docstring,因为它被认为是因为Python无法知道docstring是否还有意义(尽管继承应该足以意味着程序员遵循正常的OOP,不会完全改变对象的意义),据认为,如果文档为空白,则会减少错误引导。例如,在父类是ABC的情况下,它会变得更加复杂……在3.3中,
\uuuuuu doc\uuuuu
getset\u描述符现在可以为堆类型写入。之前,它只定义了一个
getter
;现在它有了
设置器
<代码>检查\u设置\u特殊\u类型\u属性
防止删除
\uuuuu文档
@eryksun:已确认;这是一个早就应该解决的问题!恢复了我最初仅针对Python 3.3的类装饰器想法。谢谢!最后,我使用了
\uuu init\uuu()
一个。元类要求我在很多地方拥有该元类,而不是在一个(基类)类中只有一个init。实际上,我使用了django rest框架的
init\uuuuuuuuuu()
方法中的
inspect.getmro(type(self))
解决方案。您的意思可能是
。获取描述()
而不是
。get_name()
?是的,我看到了这个,我有一个基类试图覆盖它。但我仍然无法掌握我父母的文档字符串:-)好吧,至少,那是在我读另一个答案之前。“你可能是说。get_description()而不是。get_name()”的确,是的,我做了。看看我最后是怎么做的。注意:根据Martijn的回答(),设置
。\uuu doc\uu
只在python 3.3中有效。@ReinoutvanRees,我在2.3.4(是的,两点三)和2.7中试过。在定义类后,不能将指定给._doc,如果为true,则可以在定义时指定。这就是为什么我发布了我的答案。啊,你是对的。因此,如果您记住在定义时实际分配
\uuuuuu doc\uuuu
,那么这也是一个选项。很好,很简单。