__Init __ () dovrebbe chiamare __init __ () della classe genitore?


132

Sono abituato che in Objective-C ho questo costrutto:

- (void)init {
    if (self = [super init]) {
        // init class
    }
    return self;
}

Python dovrebbe anche chiamare l'implementazione della classe genitore per __init__?

class NewClass(SomeOtherClass):
    def __init__(self):
        SomeOtherClass.__init__(self)
        # init class

È vero / falso anche per __new__()e __del__()?

Modifica: c'è una domanda molto simile: Eredità e Sostituzione __init__in Python


hai cambiato il tuo codice in modo significativo. Posso capire che l'originale objectfosse un errore di battitura. Ma ora non hai nemmeno il supertitolo della tua domanda a cui si riferisce.
SilentGhost,

Pensavo solo che super fosse usato come nome per la classe genitore. Non pensavo che qualcuno avrebbe pensato alla funzione. Mi dispiace per eventuali equivoci.
Georg Schölly,

Un motivo per cui non è automatico domanda super-chiamata: stackoverflow.com/questions/3782827/...
Ciro Santilli郝海东冠状病六四事件法轮功

Risposte:


67

In Python, chiamare la superclasse " __init__è facoltativo. Se lo chiami, è anche facoltativo se usare l' superidentificatore o se nominare esplicitamente la superclasse:

object.__init__(self)

In caso di oggetto, non è strettamente necessario chiamare il metodo super, poiché il metodo super è vuoto. Lo stesso per __del__.

D'altra parte, per __new__, dovresti davvero chiamare il metodo super e usare il suo ritorno come oggetto appena creato - a meno che tu non voglia esplicitamente restituire qualcosa di diverso.


Quindi non esiste una convenzione per chiamare l'implementazione di Super?
Georg Schölly,

5
Nelle classi vecchio stile, potresti chiamare la superinizia solo se la superclasse aveva effettivamente un init definito (cosa che spesso non lo fa). Pertanto, le persone in genere pensano di chiamare il metodo super, piuttosto che farlo per principio.
Martin v. Löwis,

1
Se la sintassi in Python fosse semplice come [super init], sarebbe più comune. Solo un pensiero speculativo; il super costrutto in Python 2.x è un po 'imbarazzante per me.
u0b34a0f6ae,

Qui sembra essere un esempio interessante (e forse contraddittorio): bytes.com/topic/python/answers/… init
mlvljr

"opzionale" in quanto non è necessario chiamarlo, ma se non lo si chiama, non verrà chiamato automaticamente.
McKay,

140

Se hai bisogno di qualcosa di super __init__da fare in aggiunta a ciò che viene fatto nelle classi attuali, __init__,devi chiamarlo da solo, poiché ciò non accadrà automaticamente. Ma se non hai bisogno di qualcosa di super, __init__,non c'è bisogno di chiamarlo. Esempio:

>>> class C(object):
        def __init__(self):
            self.b = 1


>>> class D(C):
        def __init__(self):
            super().__init__() # in Python 2 use super(D, self).__init__()
            self.a = 1


>>> class E(C):
        def __init__(self):
            self.a = 1


>>> d = D()
>>> d.a
1
>>> d.b  # This works because of the call to super's init
1
>>> e = E()
>>> e.a
1
>>> e.b  # This is going to fail since nothing in E initializes b...
Traceback (most recent call last):
  File "<pyshell#70>", line 1, in <module>
    e.b  # This is going to fail since nothing in E initializes b...
AttributeError: 'E' object has no attribute 'b'

__del__è allo stesso modo ((ma __del__fai attenzione a fare affidamento per la finalizzazione - considera invece di farlo tramite la dichiarazione with).

Uso raramente __new__. faccio tutte le inizializzazioni in__init__.


3
La definizione di classe D (C) deve essere corretta in questo modosuper(D,self).__init__()
eyquem,

11
super () .__ init __ () funziona solo in Python 3. In Python 2 hai bisogno di super (D, self) .__ init __ ()
Jacinda,

"Se avete bisogno di qualcosa da Super init ..." - Questa è una dichiarazione molto problematico, in quanto non è un caso se la si / la sottoclasse ha bisogno di "qualcosa", ma se la base di classe ha bisogno di qualcosa per essere una valida istanza della classe base e funziona correttamente. Come implementatore della classe derivata, gli interni della classe di base sono cose che non puoi / non dovresti sapere, e anche se lo fai perché hai scritto entrambi o gli interni sono documentati, il design della base potrebbe cambiare in futuro e rompersi a causa di uno scritto male classe derivata. Quindi assicurati sempre che la classe base sia inizializzata completamente.
Nick,

105

Nella risposta di Anon:
"Se hai bisogno di qualcosa di super __init__da fare in aggiunta a ciò che viene fatto nelle lezioni attuali __init__, devi chiamarlo tu stesso, dal momento che ciò non accadrà automaticamente"

È incredibile: sta formulando esattamente il contrario del principio di eredità.


Non è che "qualcosa del super __init__ (...) non accadrà automaticamente" , è che DOVREBBE accadere automaticamente, ma non succede perché la classe base " __init__è sostituita dalla definizione del clas derivato__init__

Quindi, PERCHÉ definire un deriv_class ' __init__, dal momento che ignora ciò a cui si rivolge quando qualcuno ricorre all'eredità ??

È perché bisogna definire qualcosa che NON viene fatto nella classe base ' __init__, e l'unica possibilità per ottenerlo è quella di mettere la sua esecuzione in una __init__funzione di classe derivata .
In altre parole, uno ha bisogno di qualcosa nella classe base ' __init__in aggiunta a ciò che verrebbe fatto automaticamente nella classe base' __init__se quest'ultima non fosse sostituita.
NON il contrario.


Quindi, il problema è che le istruzioni desiderate presenti nella classe base ' __init__non vengono più attivate al momento dell'istanza. Per compensare questa inattivazione, è necessario qualcosa di speciale: chiamare esplicitamente la classe base ' __init__, per MANTENERE , NON AGGIUNGERE, l'inizializzazione eseguita dalla classe base' __init__. Questo è esattamente ciò che si dice nel documento ufficiale:

Un metodo prioritario in una classe derivata potrebbe infatti voler estendere piuttosto che semplicemente sostituire il metodo della classe base con lo stesso nome. Esiste un modo semplice per chiamare direttamente il metodo della classe di base: basta chiamare BaseClassName.methodname (sé, argomenti).
http://docs.python.org/tutorial/classes.html#inheritance

Questa è tutta la storia:

  • quando l'obiettivo è MANTENERE l'inizializzazione eseguita dalla classe base, ovvero pura eredità, non è necessario nulla di speciale, si deve semplicemente evitare di definire una __init__funzione nella classe derivata

  • quando l'obiettivo è SOSTITUIRE l'inizializzazione eseguita dalla classe base, __init__deve essere definita nella classe derivata

  • quando lo scopo è quello di AGGIUNGERE i processi all'inizializzazione eseguita dalla classe base, è __init__ necessario definire una classe derivata " , comprendente una chiamata esplicita alla classe base__init__


Quello che mi sembra sorprendente nel post di Anon non è solo il fatto che esprima il contrario della teoria dell'eredità, ma che ci siano stati 5 ragazzi che lo hanno votato senza girare i capelli, e inoltre non c'è stato nessuno a cui reagire in 2 anni una discussione il cui argomento interessante deve essere letto relativamente spesso.


1
Ero sicuro che questo post sarebbe stato votato. Temo di non avere molte spiegazioni sui motivi per cui. È più facile sottovalutare che analizzare un testo che appare incomprensibile. Ho avuto molto tempo a cercare di capire il post di Anon prima di rendermi finalmente conto che era scritto in modo specioso e non molto autorevole. Forse può essere interpretato come approssimativamente giusto per qualcuno che conosce l'eredità; ma lo trovo confuso quando letto da qualcuno che ha nozioni instabili sull'eredità, un argomento non così chiaro come l'acqua di roccia in generale
eyquem

2
"Ero sicuro che questo post sarebbe stato votato ..." Il problema principale è che sei un po 'in ritardo alla festa e la maggior parte delle persone potrebbe non leggere oltre le prime risposte. Grande spiegazione a proposito +1
Gerrat

Ottima risposta è questa.
Trilarion,

3
Hai perso le parole significative "in aggiunta" nella frase che hai citato da Aaron. L'affermazione di Aaron è completamente corretta e corrisponde a ciò che si finisce per dire.
GreenAsJade,

1
Questa è la prima spiegazione che ha dato un senso alla scelta del design di Python.
Joseph Garvin,

20

Modifica : (dopo la modifica del codice)
Non c'è modo per noi di dirti se hai bisogno o meno di chiamare i tuoi genitori __init__(o qualsiasi altra funzione). L'ereditarietà ovviamente funzionerebbe senza tale chiamata. Tutto dipende dalla logica del tuo codice: ad esempio, se tutto __init__è fatto in classe genitore, puoi semplicemente saltare del __init__tutto la classe figlio .

considera il seguente esempio:

>>> class A:
    def __init__(self, val):
        self.a = val


>>> class B(A):
    pass

>>> class C(A):
    def __init__(self, val):
        A.__init__(self, val)
        self.a += val


>>> A(4).a
4
>>> B(5).a
5
>>> C(6).a
12

Ho rimosso la super chiamata dal mio esempio, voglio sapere se si dovrebbe chiamare l'implementazione di init della classe genitore o no.
Georg Schölly,

allora potresti voler modificare il titolo. ma la mia risposta è ancora valida.
SilentGhost,

5

Non esiste una regola dura e veloce. La documentazione per una classe dovrebbe indicare se le sottoclassi devono chiamare il metodo superclasse. A volte si desidera sostituire completamente il comportamento della superclasse e altre volte aumentarlo, ovvero chiamare il proprio codice prima e / o dopo una chiamata della superclasse.

Aggiornamento: la stessa logica di base si applica a qualsiasi chiamata di metodo. I costruttori a volte hanno bisogno di una considerazione speciale (poiché spesso impostano lo stato che determina il comportamento) e i distruttori perché sono costruttori paralleli (ad es. Nell'allocazione delle risorse, ad es. Connessioni al database). Ma lo stesso potrebbe valere, per esempio, per il render()metodo di un widget.

Ulteriore aggiornamento: cos'è l'OPP? Intendi OOP? No - una sottoclasse spesso deve sapere qualcosa sul design della superclasse. Non i dettagli dell'implementazione interna, ma il contratto di base che la superclasse ha con i suoi clienti (usando le classi). Ciò non viola in alcun modo i principi OOP. Ecco perché protectedè un concetto valido in OOP in generale (sebbene non, ovviamente, in Python).


Hai detto che a volte si vorrebbe chiamare il proprio codice prima della chiamata della superclasse. Per fare ciò, è necessario conoscere l'implementazione della classe genitore, che violerebbe l'OPP.
Georg Schölly,

4

IMO, dovresti chiamarlo. Se la tua superclasse è object, non dovresti, ma in altri casi penso che sia eccezionale non chiamarla. Come già risposto da altri, è molto conveniente se la tua classe non deve nemmeno sovrascrivere __init__se stessa, ad esempio quando non ha uno stato interno (aggiuntivo) da inizializzare.


2

Sì, dovresti sempre chiamare __init__esplicitamente la classe base come una buona pratica di codifica. Dimenticare di fare ciò può causare problemi sottili o errori di runtime. Questo è vero anche se__init__ non accetta alcun parametro. Questo è diverso da altri linguaggi in cui il compilatore chiamerebbe implicitamente il costruttore della classe base per te. Python non lo fa!

Il motivo principale per cui si chiama sempre la classe base _init__ è che la classe base può in genere creare una variabile membro e inizializzarla sui valori predefiniti. Quindi se non chiami init di classe base, nessuno di quel codice verrebbe eseguito e finiresti con una classe base che non ha variabili membro.

Esempio :

class Base:
  def __init__(self):
    print('base init')

class Derived1(Base):
  def __init__(self):
    print('derived1 init')

class Derived2(Base):
  def __init__(self):
    super(Derived2, self).__init__()
    print('derived2 init')

print('Creating Derived1...')
d1 = Derived1()
print('Creating Derived2...')
d2 = Derived2()

Questo stampa ..

Creating Derived1...
derived1 init
Creating Derived2...
base init
derived2 init

Esegui questo codice .

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.