2009-07-22 11 views
98

In Java, ad esempio, l'annotazione @Override non solo fornisce il controllo in fase di compilazione di una sovrascrittura, ma rende un eccellente codice di autocontrollo. Sto solo cercando la documentazione (anche se è un indicatore per alcuni checker come pylint, questo è un bonus). Posso aggiungere un commento o docstring da qualche parte, ma qual è il modo idiomatico per indicare un override in Python?In Python, come posso indicare che sto sovrascrivendo un metodo?

+10

In altre parole, non sempre indicano che stai scavalcando un metodo? Lascia che sia il lettore a scoprirlo da solo? – Bluu

+2

Sì, lo so che sembra una situazione soggetta a errori proveniente da un linguaggio compilato, ma devi solo accettarlo. In pratica non ho trovato che fosse un grosso problema (Ruby nel mio caso, non Python, ma la stessa idea) –

+0

Certo, fatto. Sia la risposta di Triptych che le risposte di mkorpela sono semplici, mi piace, ma lo spirito esplicito-oltre-implicito di quest'ultimo, e la prevenzione intelligente degli errori, vince. – Bluu

risposta

140

UPDATE (2015/05/23): Sulla base di questo e FWC: s risposta che ho creato un pacchetto installabile pip https://github.com/mkorpela/overrides

Di tanto in tanto finisco qui guardando a questa domanda. Principalmente succede dopo (di nuovo) vedere lo stesso bug nella nostra base di codice: qualcuno ha dimenticato alcune "interfacce" che implementano la classe rinominando un metodo nell'interfaccia.

Bene Python non è Java ma Python ha potere - ed esplicito è meglio che implicito - e ci sono casi concreti reali nel mondo reale in cui questa cosa mi avrebbe aiutato.

Quindi ecco uno schizzo del decoratore di overrides. Questo verificherà che la classe data come parametro abbia lo stesso metodo (o qualcosa) come metodo da decorare.

Se riesci a pensare ad una soluzione migliore, per favore postala qui!

def overrides(interface_class): 
    def overrider(method): 
     assert(method.__name__ in dir(interface_class)) 
     return method 
    return overrider 

Funziona come segue:

class MySuperInterface(object): 
    def my_method(self): 
     print 'hello world!' 


class ConcreteImplementer(MySuperInterface): 
    @overrides(MySuperInterface) 
    def my_method(self): 
     print 'hello kitty!' 

e se fate una versione difettosa esso genererà un errore di asserzione durante il caricamento della classe:

class ConcreteFaultyImplementer(MySuperInterface): 
    @overrides(MySuperInterface) 
    def your_method(self): 
     print 'bye bye!' 

>> AssertionError!!!!!!! 
+11

Fantastico. Questo ha colto un bug di errore la prima volta che l'ho provato. Complimenti. –

+1

Quindi, sto usando python 2.7 e se la mia classe estende una serie di altre classi e, a differenza dell'interfaccia, non voglio hardcodificare il nome esatto della classe che contiene la funzione di interfaccia, quindi può funzionare in generale se Io eredito da più di una classe o l'ordine di risoluzione del metodo lo romperà? –

+0

Come scriveresti un'asserzione che controlla se il numero di argomenti è lo stesso nella classe base/interfaccia e nella classe figlio? – Skarab

4

Per quanto ne so, non esiste un modo speciale per indicare un override in Python. Devi solo definire il metodo e includere una docstring, come sempre.

8

Python non è Java. Ovviamente non esiste una cosa del genere come un controllo in fase di compilazione.

Penso che un commento nella docstring sia abbondante. Ciò consente a qualsiasi utente del metodo di digitare help(obj.method) e vedere che il metodo è un override.

È inoltre possibile estendere in modo esplicito un'interfaccia con class Foo(Interface), che consentirà agli utenti di digitare help(Interface.method) per avere un'idea della funzionalità che il metodo è destinato a fornire.

+40

Il vero punto di '@ Override' in Java non è quello di documentare - è quello di catturare un errore quando si intende sovrascrivere un metodo, ma si è finito per definirne uno nuovo (ad esempio perché si è sbagliato a digitare un nome; in Java, potrebbe Accade anche perché hai usato la firma sbagliata, ma questo non è un problema in Python - ma l'errore di ortografia è ancora). –

+2

@ Pavel Minaev: Vero, ma è comunque comodo avere per la documentazione, specialmente se si sta utilizzando un IDE/editor di testo che non ha indicatori automatici per l'override (il JDT di Eclipse li mostra ordinatamente insieme ai numeri di riga, ad esempio) . –

+2

@PavelMinaev Sbagliato. Uno dei punti principali di '@ Override' è la documentazione oltre al controllo del tempo di compilazione. – siamii

8

Se si desidera che questo a scopo di documentazione unica, è possibile definire il proprio Override decoratore:

def override(f): 
    return f 


class MyClass (BaseClass): 

    @override 
    def method(self): 
     pass 

Questo non è altro che eye-candy, a meno che non si crea esclusione (f) in modo tale che sia in realtà controlla un override.

Ma allora, questo è Python, perché scrivere come se fosse Java?

+2

Si potrebbe aggiungere la validazione effettiva tramite ispezione al decoratore 'override'. –

+25

* Ma allora, questo è Python, perché scrivere come se fosse Java? * Perché alcune idee in Java sono buone e vale la pena estenderle ad altre lingue? –

+4

Perché quando si rinomina un metodo in una superclasse sarebbe bello sapere che alcuni sottoclassi di 2 livelli lo stavano sovrascrivendo. Certo, è facile da controllare, ma un piccolo aiuto dal parser della lingua non farebbe male. – Abgan

21

Ecco un'implementazione che non lo fa richiede la specifica del nome interface_class.

import inspect 
import re 

def overrides(method): 
    # actually can't do this because a method is really just a function while inside a class def'n 
    #assert(inspect.ismethod(method)) 

    stack = inspect.stack() 
    base_classes = re.search(r'class.+\((.+)\)\s*\:', stack[2][4][0]).group(1) 

    # handle multiple inheritance 
    base_classes = [s.strip() for s in base_classes.split(',')] 
    if not base_classes: 
     raise ValueError('overrides decorator: unable to determine base class') 

    # stack[0]=overrides, stack[1]=inside class def'n, stack[2]=outside class def'n 
    derived_class_locals = stack[2][0].f_locals 

    # replace each class name in base_classes with the actual class type 
    for i, base_class in enumerate(base_classes): 

     if '.' not in base_class: 
      base_classes[i] = derived_class_locals[base_class] 

     else: 
      components = base_class.split('.') 

      # obj is either a module or a class 
      obj = derived_class_locals[components[0]] 

      for c in components[1:]: 
       assert(inspect.ismodule(obj) or inspect.isclass(obj)) 
       obj = getattr(obj, c) 

      base_classes[i] = obj 


    assert(any(hasattr(cls, method.__name__) for cls in base_classes)) 
    return method 
+1

Un po 'magico ma rende l'uso tipico molto più facile. Puoi includere esempi di utilizzo? – Bluu

+0

quali sono i costi medi e peggiori dell'utilizzo di questo decoratore, forse espressi come confronto con un decoratore incorporato come @classmethod o @property? – larham1

+2

@ larham1 Questo decoratore viene eseguito una volta, quando viene analizzata la definizione della classe, non in ogni chiamata. Pertanto il suo costo di esecuzione è irrilevante rispetto al runtime del programma. – Abgan

2

Come altri hanno detto a differenza di Java non c'è tag @Overide comunque sopra potete creare i propri utilizzando decoratori tuttavia Io suggerirei di usare il getattrib() metodo globale invece di utilizzare il dict interna in modo da ottenere qualcosa di simile il seguente:

def Override(superClass): 
    def method(func) 
     getattr(superClass,method.__name__) 
    return method 

Se si voleva si poteva prendere getattr() nel proprio tentativo di cattura aumentare il proprio errore, ma penso che il metodo getattr è meglio in questo caso.

Anche questa cattura tutte le voci legate a una classe compresi i metodi di classe e vairables

0

sente è più semplice e operante sotto Jython con le classi Java:

class MyClass(SomeJavaClass): 
    def __init__(self): 
     setattr(self, "name_of_method_to_override", __method_override__) 

    def __method_override__(self, some_args): 
     some_thing_to_do() 
Problemi correlati