2012-12-18 14 views
10

Istnieje pytanie na temat Inherit docstrings in Python class inheritance, ale odpowiedzi tam odnoszą się do metod docstrukcji metod.Dziedziczenie docstringu klasy nadrzędnej jako atrybutu __doc__

Moje pytanie brzmi: jak odziedziczyć docstring klasy nadrzędnej jako atrybut __doc__. W praktyce jest to, że Django rest framework generuje ładną dokumentację w wersji HTML interfejsu API na podstawie docletów klas widoku. Ale po odziedziczeniu klasy bazowej (z docstringiem) w klasie bez docstrukcji, API nie wyświetla docstringu.

Może się zdarzyć, że sfinks i inne narzędzia zrobią to, co trzeba, i zajmą się dla mnie dziedziczeniem docstring, ale struktura reszta django wygląda na (pusty) atrybut .__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" 

Próbowałem coś podobnego super(SubClassWithoutDocstring, self).__doc__, ale również tylko dostał mi None.

Odpowiedz

11

Ponieważ nie można przypisać nową __doc__ docstring do klasy (w CPython przynajmniej), będziesz musiał użyć metaklasa:

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) 

Tak, skok przez dodatkową obręcz lub dwa, ale powyższy metaclass znajdzie poprawne, ale zawiłe, stworzysz swój wykres dziedziczenia.

Zastosowanie:

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

Alternatywą jest ustawiony __doc__ w __init__ jako zmiennej instancji:

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 

Wtedy przynajmniej instancje mają 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' 

Jako Python 3.3 (który naprawiono issue 12773), może końcu po prostu ustawić atrybut klas niestandardowych __doc__, więc wtedy można użyć klasy dekoratora Zamiast:

import inspect 

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

które następnie można zastosować następujący sposób:

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

Dodam tylko z tego, co pamiętam o dyskusjach dotyczących Pythona na ten temat. Nie dziedziczy domyślnie docstrukcji, ponieważ uważa się, że skoro Python nie może wiedzieć, czy docstring będzie już znaczący (chociaż dziedziczenie powinno wystarczyć, aby programista utrzymywał zgodność z normalnym OOP, aby nie całkowicie zmienić znaczenia obiekt), uznano, że przypadek, w którym dokument był pusty, był mniej błędny. Jest bardziej skomplikowany, gdy klasa nadrzędna jest na przykład ABC ... –

+0

W wersji 3.3 'get_desktopor' __doc__' jest teraz zapisywalny dla typów sterty. Zanim zdefiniowano tylko 'getter'; teraz ma 'setter' [' type_set_doc'] (http://hg.python.org/cpython/file/bd8afb90ebf2/Objects/typeobject.c#l632). 'check_set_special_type_attr' zapobiega usuwaniu' __doc__'. – eryksun

+0

@eryksun: Potwierdzony; to jest naprawa, która była już dawno spóźniona! Wskrzesiłem mój oryginalny pomysł na dekorator klas tylko w Pythonie 3.3. –

2

W tym konkretnym przypadku można było również przesłonić, w jaki sposób struktura REST określa nazwę, która ma być używana dla punktu końcowego, przesłonięcie metody .get_name().

Jeśli zdecydujesz się na tę trasę, prawdopodobnie będziesz chciał zdefiniować zestaw klas bazowych dla swoich widoków i zastąpić metodę w całym widoku podstawowym za pomocą prostej klasy mixin.

Na przykład:

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 

Należy również zauważyć, że metoda get_name jest traktowane jako prywatne, a to może się zmienić w pewnym momencie w przyszłości, więc trzeba by mieć oko na informacjach o wydaniu podczas uaktualniania, dla jakichkolwiek tam zmian.

+0

Prawdopodobnie masz na myśli '.get_description()' zamiast '.get_name()'? Tak, widziałem to i mam klasę bazową, na której próbuję ją zastąpić. Ale wciąż nie mogę położyć rąk na doctrach moich rodziców :-) Cóż, przynajmniej to było zanim przeczytałem drugą odpowiedź. –

+0

"Prawdopodobnie masz na myśli .get_description() zamiast .get_name()" Rzeczywiście, tak, zrobiłem. –

+0

Zobacz, jak to zrobiłem na http://reinout.vanrees.org/weblog/2012/12/19/docstring-inheritance-djangorestframework.html. –

1

Najprostszym sposobem jest przypisanie go jako zmienną klasy:

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__ 

To instrukcja, niestety, ale proste. Nawiasem mówiąc, podczas formatowania ciąg nie działa w zwykły sposób, to jednak w taki sam sposób:

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

Uwaga: zgodnie z odpowiedzią Martijna (http://stackoverflow.com/a/13937525/27401) ustawienie ".__ doc__" działa tylko w pythonie 3.3. –

+2

@ReinoutvanRees, próbowałem tego w 2.3.4 (tak, dwa punkty trzy) i 2.7. Nie możesz przypisać do .__ doc__ po zdefiniowaniu klasy, prawda, ale możesz to zrobić w momencie definicji. Dlatego wysłałem moją odpowiedź. –

+0

Ach, masz rację. Jest to również opcja, pod warunkiem, że pamiętasz o przypisaniu '__doc__' w czasie definicji. Całkiem OK i proste. –

0

Można również zrobić to za pomocą @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 

Można nawet zastąpić 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 

Jedno zastrzeżenie, obiekt nie może być dziedziczone przez innych klas widocznie, trzeba dodać właściwość w każdej klasie, który chcesz zastąpić 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 

Bummer! Ale zdecydowanie łatwiejsze niż robienie meta-klasy!

Powiązane problemy