Ereditarietà e sostituzione __init__ in python


129

Stavo leggendo "Dive Into Python" e nel capitolo sulle lezioni fornisce questo esempio:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

L'autore afferma quindi che se si desidera sovrascrivere il __init__metodo, è necessario chiamare esplicitamente il genitore __init__con i parametri corretti.

  1. E se quella FileInfoclasse avesse avuto più di una classe antenata?
    • Devo chiamare esplicitamente tutti i __init__metodi delle classi antenate ?
  2. Inoltre, devo fare questo con qualsiasi altro metodo che voglio sovrascrivere?

3
Si noti che il sovraccarico è un concetto separato dall'override.
Dana the Sane,

Risposte:


158

Il libro è un po 'datato rispetto alla chiamata sottoclasse-superclasse. È anche un po 'datato rispetto alla sottoclasse delle classi incorporate.

Oggi sembra così:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

Nota quanto segue:

  1. Siamo in grado di creare una sottoclasse direttamente classi incorporate, come dict, list, tuple, etc.

  2. La superfunzione gestisce il rintracciamento delle superclassi di questa classe e il richiamo delle funzioni in esse in modo appropriato.


5
dovrei cercare un libro / tutorial migliore?
liewl,

2
Quindi, nel caso di ereditarietà multipla, super () li rintraccia per tutti?
Dana,

dict .__ init __ (self), in realtà, ma non c'è nulla di sbagliato in questo - la chiamata super (...) fornisce solo una sintassi più coerente. (Non sono sicuro di come funzioni per l'ereditarietà multipla, penso che possa trovare solo un init per superclasse )
David Z,

4
L'intenzione di super () è che gestisce l'ereditarietà multipla. Lo svantaggio è che in pratica l'ereditarietà multipla si rompe ancora molto facilmente (vedi < fuhm.net/super-harmful ).
qc

2
Sì, in caso di ereditarietà multipla e classi base che accettano argomenti del costruttore, di solito ti ritrovi a chiamare manualmente i costruttori.
Torsten Marek,

18

In ogni classe da cui devi ereditare, puoi eseguire un ciclo di ogni classe che deve essere avviata all'avvio della classe figlio ... un esempio che può essere copiato potrebbe essere meglio compreso ...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name

2
Non sembra che il ciclo for in Child.__init__sia necessario. Quando lo rimuovo dall'esempio, il bambino stampa ancora "Nonna". Il nonno init non è gestito dalla Parentclasse?
Adam,

4
Penso che i nonni init siano già gestiti da Parent, no?
johk95,

15

Non è davvero necessario chiamare i __init__metodi della classe di base (es), ma di solito si desidera farlo, perché le classi di base faranno alcune inizializzazioni importanti c'è che sono necessari per il riposo dei metodi classi per lavoro.

Per altri metodi dipende dalle tue intenzioni. Se vuoi solo aggiungere qualcosa al comportamento delle classi di base, dovrai chiamare il metodo delle classi di base in aggiunta al tuo codice. Se si desidera cambiare radicalmente il comportamento, è possibile che non si chiami il metodo della classe base e si implementino tutte le funzionalità direttamente nella classe derivata.


4
Per completezza tecnica, alcune classi, come il threading.Thread, genereranno errori giganteschi se si tenta di evitare di chiamare init del genitore .
David Berger,

5
Trovo tutto questo discorso "non devi chiamare il costruttore della base" estremamente irritante. Non devi chiamarlo in nessuna lingua che conosco. Tutti si rovinano (o meno) più o meno allo stesso modo, non inizializzando i membri. Suggerire di non inizializzare le classi di base è sbagliato in molti modi. Se una classe non ha bisogno di inizializzazione, ora ne avrà bisogno in futuro. Il costruttore fa parte dell'interfaccia della struttura di classe / costrutto del linguaggio e deve essere utilizzato correttamente. L'uso corretto è chiamarlo qualche volta nel costruttore del tuo derivato. Allora fallo.
Andreas,

2
"Suggerire di non inizializzare le classi di base è sbagliato in tanti modi." Nessuno ha suggerito di non inizializzare la classe base. Leggi attentamente la risposta. Si tratta solo di intenzione. 1) Se vuoi lasciare la logica di init della classe base così com'è, non sovrascrivi il metodo init nella tua classe derivata. 2) Se si desidera estendere la logica di init dalla classe base, definire il proprio metodo init e quindi chiamare il metodo init della classe base da esso. 3) Se si desidera sostituire la logica di init della classe base, si definisce il proprio metodo init senza chiamare quello dalla classe base.
Wombatonfire

4

Se la classe FileInfo ha più di una classe antenata, allora dovresti assolutamente chiamare tutte le loro funzioni __init __ (). Dovresti anche fare lo stesso per la funzione __del __ (), che è un distruttore.


2

Sì, devi chiamare __init__per ogni classe genitore. Lo stesso vale per le funzioni, se si sostituisce una funzione esistente in entrambi i genitori.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.