Perché i metodi __init__ della superclasse non vengono richiamati automaticamente?


155

Perché i progettisti di Python hanno deciso che i __init__()metodi delle sottoclassi non chiamano automaticamente i __init__()metodi delle loro superclassi, come in alcune altre lingue? Il linguaggio Pythonic e raccomandato è davvero simile al seguente?

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'

2
Puoi scrivere un decoratore per ereditare il __init__metodo e forse anche cercare automaticamente le sottoclassi e decorarle.
osa,

2
@osa, sembra davvero una buona idea. ti piacerebbe descrivere qualcosa in più sulla parte decoratore?
Diansheng,

1
@osa sì descrivi di più!
Charlie Parker,

Risposte:


163

La distinzione cruciale tra i costruttori di Python __init__e quelli di altri linguaggi è che non è un costruttore: è un inizializzatore (il costruttore effettivo (se presente, ma, vedi più avanti ;-) è e funziona di nuovo in modo completamente diverso). Mentre costruire tutte le superclassi (e, senza dubbio, farlo "prima" continui a costruire verso il basso) è ovviamente parte del dire che stai costruendo l'istanza di una sottoclasse, chiaramente non è il caso dell'inizializzazione__init____new__, poiché ci sono molti casi d'uso in cui l'inizializzazione delle superclassi deve essere saltata, modificata, controllata - accadendo, se non del tutto, "nel mezzo" dell'inizializzazione della sottoclasse e così via.

Fondamentalmente, la delega di classe superiore dell'inizializzatore non è automatica in Python per gli stessi motivi per cui tale delega non è automatica per nessun altro metodo - e nota che quegli "altri linguaggi" non eseguono la delega automatica di super classe per nessuno altro metodo o ... solo per il costruttore (e, se applicabile, distruttore), che, come ho già detto, non è quello di Python __init__. (Anche il comportamento di __new__è abbastanza singolare, anche se in realtà non è direttamente correlato alla tua domanda, poiché __new__è un costruttore così peculiare che in realtà non ha necessariamente bisogno di costruire nulla - potrebbe perfettamente restituire un'istanza esistente o anche una non istanza ... chiaramente Python ti offre moltopiù controllo della meccanica rispetto alle "altre lingue" che hai in mente, il che include anche non avere una delega automatica in __new__sé! -).


7
È stato valutato perché, per me, il vero vantaggio è la possibilità che menzioni di chiamare _ init () _ della superclasse in qualsiasi momento dell'inizializzazione della sottoclasse (o per niente).
kindall

53
-1 per " __init__non è un costruttore ... l'attuale costruttore ... è __new__". Come noterai tu stesso, __new__non si comporta come un costruttore di altre lingue. __init__è infatti molto simile (viene chiamato durante la creazione di un nuovo oggetto, dopo che tale oggetto è stato allocato, per impostare le variabili membro sul nuovo oggetto), ed è quasi sempre il luogo per implementare funzionalità che in altre lingue si inseriranno un costruttore. Quindi chiamalo semplicemente costruttore!
Ben

8
Penso anche che questa sia un'affermazione piuttosto assurda: " costruire tutte le superclassi ... è ovviamente parte del dire che stai costruendo l'istanza di una sottoclasse, che chiaramente non è il caso dell'inizializzazione ". Non c'è nulla nelle parole costruzione / inizializzazione che rende questo "ovvio" per chiunque. E __new__non invoca automaticamente __new__neanche la superclasse . Quindi la tua affermazione secondo cui la distinzione chiave è che la costruzione implica necessariamente la costruzione di superclassi, mentre l' inizializzazione non è in contrasto con la tua affermazione che __new__è il costruttore.
Ben

36
Infatti, dalla documentazione Python per __init__a docs.python.org/reference/datamodel.html#basic-customization : "Come un vincolo speciale sui costruttori , nessun valore può essere restituito, così facendo causerà un TypeError di essere sollevato in fase di esecuzione "(enfasi mia). Quindi lì, è ufficiale, __init__è un costruttore.
Ben

3
Nella terminologia Python / Java, __init__viene chiamato costruttore. Questo costruttore è una funzione di inizializzazione chiamata dopo che l'oggetto è stato completamente costruito e inizializzato su uno stato predefinito incluso il suo tipo di runtime finale. Non equivale ai costruttori C ++, chiamati su un oggetto allocato tipicamente staticamente con uno stato indefinito. Anche questi sono diversi __new__, quindi abbiamo davvero almeno quattro diversi tipi di funzioni di allocazione / costruzione / inizializzazione. Le lingue usano una terminologia mista e la parte importante è il comportamento e non la terminologia.
Elazar,

36

Sono un po 'imbarazzato quando le persone pappagallo sullo "Zen di Python", come se fosse una giustificazione per qualsiasi cosa. È una filosofia di design; particolari decisioni di progettazione possono sempre essere spiegate in termini più specifici - e devono essere, altrimenti lo "Zen di Python" diventa una scusa per fare qualsiasi cosa.

Il motivo è semplice: non devi necessariamente costruire una classe derivata in modo simile a come costruisci la classe base. Potresti avere più parametri, meno, potrebbero essere in un ordine diverso o non essere affatto correlati.

class myFile(object):
    def __init__(self, filename, mode):
        self.f = open(filename, mode)
class readFile(myFile):
    def __init__(self, filename):
        super(readFile, self).__init__(filename, "r")
class tempFile(myFile):
    def __init__(self, mode):
        super(tempFile, self).__init__("/tmp/file", mode)
class wordsFile(myFile):
    def __init__(self, language):
        super(wordsFile, self).__init__("/usr/share/dict/%s" % language, "r")

Questo vale per tutti i metodi derivati, non solo __init__.


6
Questo esempio offre qualcosa di speciale? anche i linguaggi statici possono farlo
jean

18

Java e C ++ richiedono che venga chiamato un costruttore di classe base a causa del layout di memoria.

Se hai una classe BaseClasscon un membro field1e crei una nuova classe SubClassche aggiunge un membro field2, un'istanza di SubClasscontiene spazio per field1e field2. È necessario BaseClasscompilare un costruttore field1, a meno che non sia necessario che tutte le classi ereditarie ripetano BaseClassl'inizializzazione nei propri costruttori. E se field1è privato, l'ereditarietà delle classi non può essere inizializzata field1.

Python non è Java o C ++. Tutte le istanze di tutte le classi definite dall'utente hanno la stessa "forma". Sono fondamentalmente solo dizionari in cui è possibile inserire attributi. Prima di qualsiasi inizializzazione, tutte le istanze di tutte le classi definite dall'utente sono quasi identiche ; sono solo luoghi in cui archiviare attributi che non sono ancora stati archiviati.

Quindi ha perfettamente senso che una sottoclasse Python non chiami il suo costruttore di classe base. Potrebbe semplicemente aggiungere gli attributi stessi se lo desiderasse. Non c'è spazio riservato per un determinato numero di campi per ogni classe nella gerarchia e non c'è differenza tra un attributo aggiunto dal codice da un BaseClassmetodo e un attributo aggiunto dal codice da un SubClassmetodo.

Se, come è comune, in SubClassrealtà vuole avere tutti BaseClassgli invarianti impostati prima che continui a fare la propria personalizzazione, allora sì, puoi semplicemente chiamare BaseClass.__init__()(o utilizzare super, ma è complicato e ha i suoi problemi a volte). Ma non devi. E puoi farlo prima, o dopo, o con diversi argomenti. Inferno, se volevi, potresti chiamare il BaseClass.__init__da un altro metodo interamente di __init__; forse hai qualcosa di bizzarro inizializzazione pigra in corso.

Python raggiunge questa flessibilità mantenendo le cose semplici. Inizializzi gli oggetti scrivendo un __init__metodo che imposta gli attributi self. Questo è tutto. Si comporta esattamente come un metodo, perché è esattamente un metodo. Non ci sono altre regole strane e non intuitive sulle cose che devono essere fatte prima, o cose che accadranno automaticamente se non fai altre cose. L'unico scopo che deve servire è quello di essere un hook da eseguire durante l'inizializzazione dell'oggetto per impostare i valori degli attributi iniziali, e fa proprio questo. Se vuoi che faccia qualcos'altro, lo scrivi esplicitamente nel tuo codice.


2
Per quanto riguarda il C ++, non ha nulla a che fare con il "layout di memoria". Lo stesso modello di inizializzazione offerto da Python avrebbe potuto essere implementato in C ++. L'unico motivo per cui la costruzione / distruzione sono come sono in C ++ è a causa della decisione di progettazione di fornire strutture affidabili e ben comportanti per la gestione delle risorse (RAII), che potrebbero anche essere generate automagicamente (ovvero meno codice ed errori umani) dai compilatori poiché le regole per loro (il loro ordine di chiamata) sono rigorosamente definite. Non sono sicuro, ma molto probabilmente Java ha semplicemente seguito questo approccio per essere un altro linguaggio simile a POLA C.
Alexander Shukaev,

10

"Esplicito è meglio che implicito." È lo stesso ragionamento che indica che dovremmo scrivere esplicitamente 'sé'.

Penso che alla fine sia un vantaggio: puoi recitare tutte le regole che Java ha riguardo al chiamare i costruttori di superclassi?


8
Sono in gran parte d'accordo con te, ma la regola di Java è in realtà piuttosto semplice: il costruttore no-arg viene invocato a meno che tu non ne chieda specificamente uno diverso.
Laurence Gonsalves,

1
@Laurence - Cosa succede quando la classe genitore non definisce un costruttore no-arg? Cosa succede quando il costruttore no-arg è protetto o privato?
Mike Axiak,

8
Esattamente la stessa cosa che succederebbe se provassi a chiamarla esplicitamente.
Laurence Gonsalves,

8

Spesso la sottoclasse ha parametri extra che non possono essere passati alla superclasse.


7

In questo momento, abbiamo una pagina piuttosto lunga che descrive l'ordine di risoluzione del metodo in caso di ereditarietà multipla: http://www.python.org/download/releases/2.3/mro/

Se i costruttori fossero chiamati automaticamente, avresti bisogno di un'altra pagina della stessa lunghezza che spieghi l'ordine in cui ciò accade. Sarebbe un inferno ...


Questa è stata la risposta giusta Python avrebbe bisogno di definire la semantica dell'argomento passando tra sottoclasse e superclasse. Peccato che questa risposta non sia stata votata. Forse se mostrasse il problema con alcuni esempi?
Gary Weiss,

5

Per evitare confusione, è utile sapere che è possibile richiamare il __init__()metodo base_class se child_class non ha una __init__()classe.

Esempio:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

In effetti l'MRO in Python cercherà __init__()nella classe genitore quando non lo troverà nella classe figlio. È necessario richiamare direttamente il costruttore della classe genitore se si dispone già di un __init__()metodo nella classe figlio.

Ad esempio, il codice seguente restituirà un errore: class parent: def init (self, a = 1, b = 0): self.a = a self.b = b

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.

3

Forse __init__è il metodo che deve essere ignorato dalla sottoclasse. A volte le sottoclassi hanno bisogno della funzione del genitore per essere eseguite prima di aggiungere un codice specifico della classe, e altre volte devono impostare variabili di istanza prima di chiamare la funzione del genitore. Dal momento che Python non può in alcun modo sapere quando sarebbe più appropriato chiamare quelle funzioni, non dovrebbe indovinare.

Se quelli non ti influenzano, considera che __init__è solo un'altra funzione. Se invece la funzione in questione fosse dostuff, vorresti comunque che Python chiami automaticamente la funzione corrispondente nella classe genitore?


2

credo che una considerazione molto importante qui sia che con una chiamata automatica a super.__init__()te, ti priscrivi, in base alla progettazione, quando viene chiamato quel metodo di inizializzazione e con quali argomenti. evitare di chiamarlo automaticamente e richiedere al programmatore di fare esplicitamente quella chiamata, implica molta flessibilità.

dopo tutto, solo perché la classe B è derivata dalla classe A non significa che A.__init__()può o deve essere chiamata con gli stessi argomenti di B.__init__(). rendere esplicita la chiamata significa che un programmatore può avere, ad esempio, definire B.__init__()con parametri completamente diversi, fare un po 'di calcolo con quei dati, chiamare A.__init__()con argomenti appropriati per quel metodo, e quindi fare un po' di postelaborazione. questo tipo di flessibilità sarebbe imbarazzante da ottenere se A.__init__()fosse chiamato B.__init__()implicitamente, prima di essere B.__init__()eseguito o subito dopo.

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.