Utilizzo della docstring di un metodo per sovrascrivere automaticamente quella di un altro metodo
Domanda
Il problema:Ho una classe che contiene un metodo template execute
che chiama un altro metodo _execute
.Si suppone che le sottoclassi vengano sovrascritte _execute
per implementare alcune funzionalità specifiche.Questa funzionalità dovrebbe essere documentata nella docstring di _execute
.Gli utenti avanzati possono creare le proprie sottoclassi per estendere la libreria.Tuttavia, un altro utente che ha a che fare con tale sottoclasse dovrebbe utilizzare solo execute
, quindi non vedrà la docstring corretta se la utilizza help(execute)
.
Sarebbe quindi carino modificare la classe base in modo tale che in una sottoclasse la docstring of execute
viene automaticamente sostituito con quello di _execute
.Qualche idea su come potrebbe essere fatto?
Stavo pensando alle metaclassi per fare questo, per renderlo completamente trasparente all'utente.
Soluzione
Bene, se non ti dispiace copiare il metodo originale nella sottoclasse, puoi utilizzare la seguente tecnica.
import new
def copyfunc(func):
return new.function(func.func_code, func.func_globals, func.func_name,
func.func_defaults, func.func_closure)
class Metaclass(type):
def __new__(meta, name, bases, attrs):
for key in attrs.keys():
if key[0] == '_':
skey = key[1:]
for base in bases:
original = getattr(base, skey, None)
if original is not None:
copy = copyfunc(original)
copy.__doc__ = attrs[key].__doc__
attrs[skey] = copy
break
return type.__new__(meta, name, bases, attrs)
class Class(object):
__metaclass__ = Metaclass
def execute(self):
'''original doc-string'''
return self._execute()
class Subclass(Class):
def _execute(self):
'''sub-class doc-string'''
pass
Altri suggerimenti
C'è un motivo per cui non puoi sovrascrivere la classe base execute
funzionare direttamente?
class Base(object):
def execute(self):
...
class Derived(Base):
def execute(self):
"""Docstring for derived class"""
Base.execute(self)
...stuff specific to Derived...
Se non vuoi fare quanto sopra:
Gli oggetti metodo non supportano la scrittura nel file __doc__
attributo, quindi devi cambiarlo __doc__
nell'oggetto funzione reale.Dato che non vuoi sovrascrivere quello nella classe base, dovresti dare a ciascuna sottoclasse la propria copia di execute
:
class Derived(Base):
def execute(self):
return Base.execute(self)
class _execute(self):
"""Docstring for subclass"""
...
execute.__doc__= _execute.__doc__
ma questo è simile a un modo indiretto di ridefinizione execute
...
Guarda il decoratore functools.wraps();fa tutto questo, ma non so subito se riesci a farlo funzionare nel giusto contesto
Bene, la doc-string è archiviata in __doc__
quindi non sarebbe troppo difficile riassegnarlo in base alla stringa di documento di _execute
dopo il fatto.
Fondamentalmente:
class MyClass(object):
def execute(self):
'''original doc-string'''
self._execute()
class SubClass(MyClass):
def _execute(self):
'''sub-class doc-string'''
pass
# re-assign doc-string of execute
def execute(self,*args,**kw):
return MyClass.execute(*args,**kw)
execute.__doc__=_execute.__doc__
Execute deve essere dichiarato nuovamente in modo che la stringa doc venga allegata alla versione diexecute per il SubClass
e non per MyClass
(che altrimenti interferirebbe con altre sottoclassi).
Non è un modo molto ordinato per farlo, ma dal punto di vista dell'utente di una libreria dovrebbe dare il risultato desiderato.Potresti quindi racchiuderlo in una meta-classe per renderlo più semplice per le persone che stanno sottoclassando.
Sono d'accordo sul fatto che il modo più semplice e più pitonico per affrontare questo problema è semplicemente ridefinire l'esecuzione nelle sottoclassi e fare in modo che chiami il metodo di esecuzione della classe base:
class Sub(Base):
def execute(self):
"""New docstring goes here"""
return Base.execute(self)
Questo è pochissimo codice per realizzare ciò che desideri;l'unico svantaggio è che devi ripetere questo codice in ogni sottoclasse che estende Base.Tuttavia, questo è un piccolo prezzo da pagare per il comportamento desiderato.
Se desideri un modo sciatto e dettagliato per assicurarti che la docstring per l'esecuzione sia generata dinamicamente, puoi utilizzare il protocollo descrittore, che richiederebbe molto meno codice rispetto alle altre proposte qui.Questo è fastidioso perché non puoi semplicemente impostare un descrittore su una funzione esistente, il che significa che l'esecuzione deve essere scritta come una classe separata con un __call__
metodo.
Ecco il codice per farlo, ma tieni presente che il mio esempio sopra è molto più semplice e più Pythonic:
class Executor(object):
__doc__ = property(lambda self: self.inst._execute.__doc__)
def __call__(self):
return self.inst._execute()
class Base(object):
execute = Executor()
class Sub(Base):
def __init__(self):
self.execute.inst = self
def _execute(self):
"""Actually does something!"""
return "Hello World!"
spam = Sub()
print spam.execute.__doc__ # prints "Actually does something!"
help(spam) # the execute method says "Actually does something!"