2012-12-18 8 views
10

C'è una domanda su Inherit docstrings in Python class inheritance, ma le risposte riguardano le docstring del metodo.Eredita una docstring della classe genitore come attributo __doc__

La mia domanda è come ereditare una docstring di una classe genitore come attributo __doc__. Il caso è che Django rest framework genera una buona documentazione nella versione html dell'API basata sulle docstring delle classi di visualizzazione. Ma quando si eredita una classe base (con una docstring) in una classe senza una docstring, l'API non mostra la docstring.

Potrebbe benissimo essere che la sfinge e altri strumenti fanno la cosa giusta e gestiscono l'ereditarietà di doctring per me, ma il framework per il resto di django esamina l'attributo (vuoto) .__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" 

Ho provato qualcosa di simile super(SubClassWithoutDocstring, self).__doc__, ma che anche solo mi ha preso un None.

risposta

11

Dal momento che non è possibile assegnare un nuovo __doc__ docstring a una classe (in CPython almeno), si dovrà utilizzare un metaclasse:

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) 

Sì, abbiamo saltare attraverso un cerchio o due, ma il metaclass di cui sopra troverà lo __doc__ corretto, ma in modo convoluto si crea il grafico dell'ereditarietà.

Usage:

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

L'alternativa è quella di impostare __doc__ in __init__, come una variabile di istanza:

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 

Allora almeno le istanze hanno un 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' 

A partire da Python 3.3 (che ha risolto issue 12773), tu può finalmente è sufficiente impostare l'attributo __doc__ di classi personalizzate, in modo quindi è possibile utilizzare un decoratore di classe, invece:

import inspect 

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

che poi possono essere applicati in tal modo:

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

Aggiungo solo ciò che ricordo delle discussioni che ho avuto su Python riguardo a questo. Di default non eredita le docstring perché è considerato che dal momento che Python non può sapere se la docstring sarebbe più significativa (anche se l'ereditarietà dovrebbe essere sufficiente per dire che il programmatore è in linea con il normale OOP per non cambiare completamente il significato di un oggetto), è stato considerato il caso in cui il doc era vuoto sarebbe stato meno fuorviante. Diventa più complesso dove la classe genitore è un ABC, per esempio ... –

+0

Nel 3.3 il '__doc__'' getset_descriptor' è scrivibile per i tipi di heap. Prima aveva solo un 'getter' definito; ora ha il 'setter' [' type_set_doc'] (http://hg.python.org/cpython/file/bd8afb90ebf2/Objects/typeobject.c#l632). 'check_set_special_type_attr' impedisce l'eliminazione di' __doc__'. – eryksun

+0

@eryksun: confermato; questa è una correzione che era in ritardo da tempo! Risuscitato la mia originale idea di decoratore di classe solo per Python 3.3. –

2

In questo caso particolare si potrebbe sovrascrivere anche il modo in cui il framework REST determina il nome da utilizzare per l'endpoint, ignorando il metodo .get_name().

Se si intraprende quella rotta probabilmente ci si troverà a voler definire un set di classi di base per le visualizzazioni e si sostituirà al metodo su tutta la vista di base utilizzando una semplice classe di mixaggio.

Ad esempio:

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 

Si noti inoltre che il metodo get_name sono considerate private, ed è destinata a cambiare ad un certo punto, in futuro, in modo si avrebbe bisogno di tenere sotto controllo le note di rilascio per l'aggiornamento, per eventuali modifiche lì.

+0

Probabilmente vuoi dire ".get_description()" invece di ".get_name()"? Sì, l'ho visto e ho una classe base in cui sto cercando di sovrascriverlo. Ma non riesco ancora a mettere le mani sulle didascalie dei miei genitori :-) Beh, almeno, questo era prima che legassi l'altra risposta. –

+0

"Probabilmente vuoi dire .get_description() invece di .get_name()" Sì, sì, sì. –

+0

Vedere http://reinout.vanrees.org/weblog/2012/12/19/docstring-inheritance-djangorestframework.html per come l'ho fatto alla fine. –

1

Il modo più semplice è quello di assegnarlo come una variabile di classe:

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__ 

E 'manuale, purtroppo, ma semplice. Per inciso, mentre formattazione di stringhe non funziona come al solito, lo fa con lo stesso metodo:

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

Nota: in base alla risposta di Martijn (http://stackoverflow.com/a/13937525/27401), l'impostazione di '.__ doc__' funziona solo in python 3.3. –

+2

@ReinoutvanRees, ho provato questo in 2.3.4 (sì, due punti tre) e 2.7. Non puoi assegnare a .__ doc__ dopo che la classe è stata definita, true, ma puoi farlo al momento della definizione. Ecco perché ho postato la mia risposta. –

+0

Ah, hai ragione. Anche quella sarebbe un'opzione, a patto che ti ricordi di assegnare effettivamente il '__doc__' al momento della definizione. Abbastanza OK e semplice. –

0

È anche possibile farlo utilizzando @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 

È anche possibile sovrascrivere un 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 

Un avvertimento, la proprietà non può essere ereditata da altre classi, evidentemente, si deve aggiungere la proprietà in ogni classe che si desidera sovrascrivere il 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! Ma sicuramente più facile che fare la cosa della meta-classe!