2012-12-18 11 views
10

Es gibt eine Frage über Inherit docstrings in Python class inheritance, aber die Antworten dort befassen sich mit Methode Docstrings.Vererben Sie eine übergeordnete Klasse docstring als __doc__ Attribut

Meine Frage ist, wie man einen Docstring einer Elternklasse als __doc__ Attribut erbt. Der Anwendungsfall ist, dass Django rest framework nette Dokumentation in der HTML-Version Ihrer API basierend auf den Docstrings Ihrer Ansichtsklassen generiert. Wenn Sie jedoch eine Basisklasse (mit einem Docstring) in einer Klasse ohne Docstring erben, zeigt die API den Docstring nicht an.

Es kann sehr gut sein, dass Sphinx und andere Tools das Richtige tun und die Docstring-Vererbung für mich behandeln, aber django Rest Framework schaut auf das (leere) .__doc__ Attribut.

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" 

Ich habe so etwas wie super(SubClassWithoutDocstring, self).__doc__ versucht, aber dass auch hat mich nur ein None.

Antwort

11

Da Sie keine neue __doc__ docstring zu einer Klasse (in CPython mindestens) zuweisen können, werden Sie eine Metaklasse verwenden müssen:

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) 

Ja, wir durch einen zusätzlichen Reifen springen oder zwei, aber Die obige Metaklasse wird die richtige __doc__ finden, aber gewunden machen Sie Ihr Vererbungsdiagramm.

Verbrauch:

>>> class ParentWithDocstring(object): 
...  """Parent docstring""" 
... 
>>> class SubClassWithoutDocstring(ParentWithDocstring): 
...  __metaclass__ = inheritdocstring 
... 
>>> SubClassWithoutDocstring.__doc__ 
'Parent docstring' 

Die Alternative ist __doc__ in __init__, als Instanzvariable zu setzen:

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 

Dann zumindest Ihre Instanzen haben eine 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' 

Ab Python 3.3 (die behoben issue 12773), Sie kann schließlich eingestellt gerade das __doc__ Attribut von benutzerdefinierten Klassen, so ist, dann können Sie stattdessen eine Klasse Dekorateur verwenden:

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

Ich füge nur etwas hinzu, woran ich mich erinnere, über die Diskussionen, die ich bezüglich Python darüber geführt habe. Es erbt Docstrings nicht standardmäßig, weil Python nicht wissen kann, ob der Docstring mehr Bedeutung hat (obwohl Vererbung ausreichen sollte, um zu bedeuten, dass der Programmierer sich an die normale OOP hält, um die Bedeutung von Objekt), es wurde der Fall angenommen, wo das Dokument leer war, würde weniger falsch führen. Es wird komplexer, wenn die Elternklasse zum Beispiel ein ABC ist ... –

+0

In 3.3 ist der '__doc__'' getset_descriptor' nun beschreibbar für Heap-Typen. Zuvor hatte es nur einen "Getter" definiert; jetzt hat es den 'setter' [type_set_doc'] (http://hg.python.org/cpython/file/bd8afb90ebf2/Objects/typeobject.c# l632). 'check_set_special_type_attr 'verhindert das Löschen von' __doc__'. – eryksun

+0

@eryksun: Bestätigt; Das ist eine Lösung, die längst überfällig war! Meine ursprüngliche Klasse Dekorateur Idee nur für Python 3.3 wiederbelebt. –

2

Sie könnten in diesem speziellen Fall:

import inspect 

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

die dann so angewendet werden, Überschreiben Sie auch, wie das REST-Framework den Namen für den Endpunkt festlegt, indem Sie die Methode .get_name() überschreiben.

Wenn Sie diesen Weg gehen, werden Sie wahrscheinlich eine Reihe von Basisklassen für Ihre Ansichten definieren und die Methode in Ihrer gesamten Basisansicht mit einer einfachen Mixinklasse überschreiben wollen.

Zum Beispiel:

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 

Beachten Sie auch, dass die get_name Methode privat betrachtet wird, und wird wahrscheinlich irgendwann in der Zukunft ändern, so müssten Sie halten Registerkarten auf den Release Notes bei der Aktualisierung, für irgendwelche Änderungen dort.

+0

Sie meinen wahrscheinlich '.get_description()' anstelle von '.get_name()'? Ja, ich habe das gesehen und ich habe eine Basisklasse, in der ich versuche, sie zu überschreiben. Aber ich kann immer noch nicht die Docstrings meiner Eltern in die Finger bekommen :-) Nun, zumindest war das, bevor ich die andere Antwort gelesen habe. –

+0

"Sie meinen wahrscheinlich .get_description() statt .get_name()" Ja, tatsächlich. –

+0

Siehe http://reinout.vanrees.org/weblog/2012/12/19/docstring-inheritance-djangorestframework.html für wie ich es am Ende gemacht habe. –

1

Der einfachste Weg ist es als Klassenvariable zuzuweisen:

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__ 

Es ist Handbuch, leider, aber unkompliziert. Übrigens, während String Formatierung nicht die übliche Art und Weise funktioniert, tut es mit der gleichen Methode:

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

Hinweis: Laut Martijns Antwort (http://stackoverflow.com/a/13937525/27401) funktioniert die Einstellung '.__ doc__' nur in Python 3.3. –

+2

@ReinoutvanRees, versuchte ich dies in 2.3.4 (ja, zwei Punkte drei) und 2.7. Sie können .__ doc__ nicht zuordnen, nachdem die Klasse definiert wurde, true, aber Sie können dies zum Zeitpunkt der Definition tun. Deshalb habe ich meine Antwort gepostet. –

+0

Ah, du hast Recht. Das wäre also auch eine Option, vorausgesetzt Sie erinnern sich, dass Sie den '__doc__' zur Definitionszeit zuweisen. Ganz OK und einfach. –

0

können Sie auch @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 

mit es tun können Sie sogar einen docstring überschreiben.

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 

Eine Einschränkung kann die Eigenschaft nicht offenbar von anderen Klassen geerbt werden, Sie haben die Eigenschaft, in jeder Klasse, dass Sie das docstring überschrieben werden sollen hinzuzufügen.

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 

Bumm! Aber definitiv einfacher als die Meta-Klasse!