Utilizzo della docstring di un metodo per sovrascrivere automaticamente quella di un altro metodo

StackOverflow https://stackoverflow.com/questions/71817

  •  09-06-2019
  •  | 
  •  

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.

È stato utile?

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!"
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top