2010-10-16 24 views
9

Esiste un modo per impedire a una classe figlia di eseguire l'override di un metodo nella classe base?Impedire la funzione ignorare in Python

La mia ipotesi è che non ci sia, ma vengo dal mondo .NET, e sto cercando di rendere la mia API più solida possibile, quindi ogni input è molto apprezzato.

class Parent: 
    def do_something(self): 
     '''This is where some seriously important stuff goes on''' 
     pass 

class Child(Parent): 
    def do_something(self): 
     '''This should not be allowed.''' 
     pass 

È possibile far rispettare questo? So che il compilatore non aiuterà, quindi forse per mezzo di alcuni controlli di runtime? O non è solo un modo pitonico di parlare delle cose?

+3

In breve, non proprio in python. http://stackoverflow.com/questions/2425656/how-to-prevent-a-function-from-being-overridden-in-python – birryree

+3

Btw, questo mi ha fatto pensare, rileggere e crack su http://steve-yegge.blogspot.com/2010/07/wikileaks-to-leak-5000-open-source-java.html - grazie! – delnan

+0

@delnan grazie per il link. Roba buona! –

risposta

11

Hai ragione: ciò che stai tentando è contrario alla struttura di Python e alla sua cultura.

Documenta la tua API e istruisci i tuoi utenti su come utilizzarlo. È il loro programma, quindi se vogliono ancora scavalcare la tua funzione, chi sei tu per prevenirli?

+0

Buon punto, grazie! –

+3

Questo è un principio di buona progettazione del programma per un sistema complesso, a parte le convenzioni linguistiche. Esempio: si dispone di un framework di modulo che espone un'interfaccia per tutti i moduli che la sottoclasse chiamata "run()". Nella superclasse, run() esegue alcuni passaggi pre/post-elaborazione interni comuni a tutti i moduli (impostando un contrassegno self.hasRun, ad esempio) ed esegue self.runBody().All'interno delle sottoclassi, il corpo effettivo del codice da eseguire è all'interno del metodo runBody(). Per far rispettare la progettazione sicura dei moduli, voglio impedire a run() di essere sovrascritto. Risolvi il mio problema in modo Python :) – Will

+2

(In Java, dichiarerei run() come finale nella superclasse e dichiarerò runBody come astratto.) – Will

7

Se un'API consente di fornire sottoclassi di una determinata classe e chiama i metodi (legalmente) sovrascritti, ma anche altri metodi API di quella classe con nomi semplici come "aggiungi", sovrascrivendo accidentalmente tali metodi potrebbe portare a -track-down bugs. È meglio avvisare almeno l'utente.

I casi in cui un utente desidera/ha bisogno di eseguire l'override di un metodo che interromperà completamente l'API è praticamente pari a zero. I casi in cui un utente ignora per errore qualcosa che non dovrebbe e ha bisogno di ore per trovare il colpevole sono molto più frequenti. Il debug del comportamento errato causato da questo può essere ingombrante.

Questo è il modo che uso per mettere in guardia o proteggere gli attributi impedire che vengano accidentalmente sovrascritti:

def protect(*protected): 
    """Returns a metaclass that protects all attributes given as strings""" 
    class Protect(type): 
     has_base = False 
     def __new__(meta, name, bases, attrs): 
      if meta.has_base: 
       for attribute in attrs: 
        if attribute in protected: 
         raise AttributeError('Overriding of attribute "%s" not allowed.'%attribute) 
      meta.has_base = True 
      klass = super().__new__(meta, name, bases, attrs) 
      return klass 
    return Protect 

è possibile utilizzarlo in questo modo:

class Parent(metaclass=protect("do_something", "do_something_else")): 
    def do_something(self): 
     '''This is where some seriously important stuff goes on''' 
     pass 

class Child(Parent): 
    def do_something(self): 
     '''This will raise an error during class creation.''' 
     pass 
3

Uzumaki già fornito una metaclasse come possibile soluzione alla domanda posta sopra, ma qui c'è un altro esempio di utilizzo. Dopo aver tentato di creare una classe Child, viene mostrato un altro modo per rendere difficile l'override dei metodi. Mettendo due underscore prima, ma non dopo un nome di attributo, verrà automaticamente richiamato il nome del mangling. Vedere this answer per un'altra domanda per un modo facile da usare per accedere a questa abilità manualmente.

#! /usr/bin/env python3 
class Access(type): 

    __SENTINEL = object() 

    def __new__(mcs, name, bases, class_dict): 
     private = {key 
        for base in bases 
        for key, value in vars(base).items() 
        if callable(value) and mcs.__is_final(value)} 
     if any(key in private for key in class_dict): 
      raise RuntimeError('certain methods may not be overridden') 
     return super().__new__(mcs, name, bases, class_dict) 

    @classmethod 
    def __is_final(mcs, method): 
     try: 
      return method.__final is mcs.__SENTINEL 
     except AttributeError: 
      return False 

    @classmethod 
    def final(mcs, method): 
     method.__final = mcs.__SENTINEL 
     return method 


class Parent(metaclass=Access): 

    @Access.final 
    def do_something(self): 
     """This is where some seriously important stuff goes on.""" 
     pass 


try: 
    class Child(Parent): 

     def do_something(self): 
      """This should not be allowed.""" 
      pass 
except RuntimeError: 
    print('Child cannot be created.') 


class AnotherParent: 

    def __do_something(self): 
     print('Some seriously important stuff is going on.') 

    def do_parent_thing(self): 
     self.__do_something() 


class AnotherChild(AnotherParent): 

    def __do_something(self): 
     print('This is allowed.') 

    def do_child_thing(self): 
     self.__do_something() 


example = AnotherChild() 
example.do_parent_thing() 
example.do_child_thing() 
Problemi correlati