【问题标题】:Inherit a parent class docstring as __doc__ attribute继承父类文档字符串作为 __doc__ 属性
【发布时间】:2012-12-05 22:14:18
【问题描述】:

有一个关于Inherit docstrings in Python class inheritance 的问题,但那里的答案涉及方法文档字符串。

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

很可能是 sphinx 和其他工具做了正确的事情并为我处理了文档字符串继承,但 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

【问题讨论】:

    标签: python docstring django-rest-framework


    【解决方案1】:

    您也可以使用@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
    

    真糟糕!但绝对比做元类的事情容易!

    【讨论】:

      【解决方案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'>)
      

      【讨论】:

      • 注意:根据Martijn的回答(stackoverflow.com/a/13937525/27401),设置.__doc__仅适用于python 3.3。
      • @ReinoutvanRees,我在 2.3.4(是的,两点三)和 2.7 中试过这个。在定义类之后,您不能分配给 .__doc__ ,这是真的,但您可以在定义时进行分配。这就是我发布答案的原因。
      • 啊,你是对的。如果您记得在定义时实际分配__doc__,那么这也是一个选项。很好很简单。
      【解决方案3】:

      由于您无法将新的 __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'
      

      另一种方法是在__init__ 中设置__doc__,作为实例变量:

      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(修复了 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'
      

      【讨论】:

      • 我将根据我所记得的关于我就 Python 进行过的讨论添加一些内容。它默认不继承文档字符串,因为它被认为是因为 Python 无法知道文档字符串是否有意义(尽管继承应该足以意味着程序员与正常的 OOP 保持一致,不会完全改变对象),则认为文档为空白的情况会减少误导。例如,如果父类是 ABC,它会变得更加复杂......
      • 在 3.3 中,__doc__ getset_descriptor 现在可用于堆类型。之前它只定义了一个getter;现在它有settertype_set_doccheck_set_special_type_attr 阻止删除 __doc__
      • @eryksun:已确认;这是一个早就应该解决的问题!只为 Python 3.3 恢复了我最初的类装饰器想法。
      • 谢谢!最后我使用了__init__() 一个。元类要求我在很多地方都有那个元类,而不是一个(基)类中只有一个 init
      • 其实我在django rest框架的.get_description()方法中使用了.__init__()中你的inspect.getmro(type(self))解决方案,但是思路是一样的。
      【解决方案4】:

      在这种特殊情况下,您还可以通过覆盖 .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 方法被认为是私有的,并且可能会在将来的某个时候更改,因此您需要在升级时密切关注发行说明,以了解其中的任何更改。

      【讨论】:

      • 您的意思可能是.get_description() 而不是.get_name()?是的,我见过那个,我有一个基类,我试图覆盖它。但我仍然无法接触到我父母的文档字符串 :-) 好吧,至少,那是在我阅读其他答案之前。
      • "你可能指的是 .get_description() 而不是 .get_name()" 确实,是的。
      • 查看reinout.vanrees.org/weblog/2012/12/19/… 了解我最后是如何做到的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多