2012-12-18 65 views
10

有一個關於Inherit docstrings in Python class inheritance的問題,但其中的答案處理方法docstrings。繼承父類docstring作爲__doc__屬性

我的問題是如何繼承父類的文檔字符串作爲__doc__屬性。用例是Django rest framework基於您的視圖類的文檔字符串在您的API的html版本中生成很好的文檔。但是,在沒有docstring的類中繼承基類(帶有docstring)時,API不會顯示文檔字符串。

它可能是非常好的,獅身人面像和其他工具做正確的事情,併爲我處理文檔字符串繼承,但django休息框架看看(空).__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

回答

11

由於無法分配新__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____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開始(它修復了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

我只是從我記得有關的討論,我曾就Python的這個補充。它沒有默認繼承文檔字符串,因爲它被認爲是因爲Python無法知道文檔字符串是否會有意義(儘管繼承應該足以意味着程序員與正常的OOP保持一致,不會完全改變對象),人們認爲這種情況下,文檔是空白的將不太引人注目。在父類是ABC的情況下,它變得更加複雜...... –

+0

在3.3中'__doc__'' getset_descriptor'現在可寫入堆類型。在它只定義了一個'getter'之前;現在它有'setter' ['type_set_doc'](http://hg.python.org/cpython/file/bd8afb90ebf2/Objects/typeobject.c#l632)。 'check_set_special_type_attr'防止刪除'__doc__'。 – eryksun

+0

@eryksun:確認;這是一個姍姍來遲的修復!只復活了我原始的類裝飾器創意,僅適用於Python 3.3。 –

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方法被認爲是私有的,並有可能在未來的某個時刻改變,所以你需要升級時密切關注的發行說明,對於那裏的任何改變。

+0

您可能是指'.get_description()'而不是'.get_name()'?是的,我已經看到了一個,我有一個基礎類,我試圖覆蓋它。但是我仍然無法依靠父母的文檔:-)至少,那是在我讀到其他答案之前。 –

+0

「你可能的意思是.get_description()而不是.get_name()」的確,是的,我做到了。 –

+0

請參閱http://reinout.vanrees.org/weblog/2012/12/19/docstring-inheritance-djangorestframework.html,瞭解我是如何做到的。 –

1

最簡單的方法是將其指定爲一個類變量:

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'>) 
+0

注意:根據Martijn的回答(http://stackoverflow.com/a/13937525/27401),設置'.__ doc__'只適用於python 3.3。 –

+2

@ReinoutvanRees,我在2.3.4(是的,兩點三)和2.7中試過。在定義類之後,您不能指定給.__ doc__,但是您可以在定義時進行。這就是我發佈我的答案的原因。 –

+0

啊,你說得對。所以這也是一個選項,只要你記得在定義時間真正地分配'__doc__'。很好,很簡單。 –

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 

無賴!但絕對比元類更容易!