2012-12-18 20 views
10

Inherit docstrings in Python class inheritanceについて質問がありますが、その答えはメソッドdocstringsを処理します。親クラスdocstringを__doc__属性として継承する

私の質問は、親クラスのドキュメントストリングを__doc__属性として継承する方法です。ユースケースは、Django rest frameworkが、ビュークラスのdocstringsに基づいてAPIのhtml版で素敵なドキュメントを生成するということです。しかし、docstringのないクラスの基本クラス(docstring付き)を継承すると、APIはdocstringを表示しません。

それは非常によく、スフィンクスや他のツールは、正しいことを行うと私のためのドキュメント文字列の継承を扱うが、残りのフレームワークは、(空の).__doc__属性を見てDjangoのことかもしれません。

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を得ました。あなたは(少なくともCPythonとの)クラスに新しい__doc__ドキュメンテーション文字列を割り当てることはできませんので、あなたはメタクラスを使用する必要があります

答えて

11

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) 

はい、私たちは余分なフープまたは2を介してジャンプが、上記のメタクラスは正確な__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)から、あなたは

その後、これを適用することができ
import inspect 

def inheritdocstring(cls): 
    for base in inspect.getmro(cls): 
     if base.__doc__ is not None: 
      cls.__doc__ = base.__doc__ 
      break 
    return cls 

を::は、最終的にはちょうどので、あなたは代わりにクラスのデコレータを使用することができ、カスタムクラスの__doc__属性を設定することができますができた。この特定のケースで

>>> @inheritdocstring 
... class SubClassWithoutDocstring(ParentWithDocstring): 
...  pass 
... 
>>> SubClassWithoutDocstring.__doc__ 
'Parent docstring' 
+1

私は、これについてPythonに関する議論について思い出しています。 Pythonはdocstringがもはや意味を持つかどうかを知ることができないので、docstringはデフォルトで継承されません(継承は、プログラマが通常の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_name()'ではなく '.get_description()'を意味しますか?はい、私はそれを見て、私はそれを上書きしようとしているベースクラスを持っている。しかし、私はまだ私の両親のドキュメントストリングに私の手を置くことはできません:-)まあ、少なくとも、私は他の答えを読む前にしていた。 –

+0

"おそらくあなたは.get_name()の代わりに.get_description()を意味します。"確かにそうでした。 –

+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ポイント3)と2.7でこれを試しました。クラスが定義された後は.__ doc__に代入することはできませんが、定義時に行うことができます。それが私の答えを掲載した理由です。 –

+0

ああ、そうです。つまり、定義時に '__doc__'を実際に割り当てることを忘れない限り、これもオプションになります。まあまあOK。 –

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 

1つの警告は、プロパティは、あなたはドキュメンテーション文字列を上書きする各クラスにプロパティを追加する必要があり、明らかに他のクラスによって継承することはできません。

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 

バマー!しかし、間違いなく簡単にメタクラスのことをやって!

関連する問題