Sollte __init __ () die __init __ () der Elternklasse aufrufen?

132

Ich bin es gewohnt, dass ich in Objective-C dieses Konstrukt habe:

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

Sollte Python auch die Implementierung der übergeordneten Klasse aufrufen __init__?

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

Ist das auch wahr / falsch für __new__()und __del__()?

Bearbeiten: Es gibt eine sehr ähnliche Frage: Vererbung und Überschreiben __init__in Python

Georg Schölly
quelle
Sie haben Ihren Code erheblich geändert. Ich kann verstehen, dass das Original objectein Tippfehler war. Aber jetzt haben Sie nicht einmal den superTitel, auf den sich Ihre Frage bezieht.
SilentGhost
Ich dachte nur, dass super als Name für die Elternklasse verwendet wird. Ich hätte nicht gedacht, dass irgendjemand an die Funktion denken würde. Es tut mir leid für etwaige Missverständnisse.
Georg Schölly
Eine warum nicht automatische Super-Call-Frage: stackoverflow.com/questions/3782827/…
Ciro Santilli 6 冠状 病 六四 事件 6

Antworten:

67

In Python ist das Aufrufen der Superklasse __init__optional. Wenn Sie es aufrufen, ist es auch optional, ob Sie den superBezeichner verwenden oder die Superklasse explizit benennen möchten:

object.__init__(self)

Im Falle eines Objekts ist das Aufrufen der Super-Methode nicht unbedingt erforderlich, da die Super-Methode leer ist. Gleiches gilt für __del__.

Andererseits __new__sollten Sie in der Tat die Super-Methode aufrufen und ihre Rückgabe als neu erstelltes Objekt verwenden - es sei denn, Sie möchten ausdrücklich etwas anderes zurückgeben.

Martin v. Löwis
quelle
Es gibt also keine Konvention, um nur die Implementierung von Super zu nennen?
Georg Schölly
5
In Klassen alten Stils konnte man die Super- Init nur aufrufen, wenn für die Super-Klasse tatsächlich eine Init definiert war (was häufig nicht der Fall ist). Daher denken die Leute normalerweise darüber nach, die Supermethode aufzurufen, anstatt sie aus Prinzip zu tun.
Martin v. Löwis
1
Wenn die Syntax in Python so einfach wie wäre [super init], wäre es üblicher. Nur ein spekulativer Gedanke; Das Superkonstrukt in Python 2.x ist für mich etwas umständlich.
u0b34a0f6ae
Hier scheint ein interessantes (und möglicherweise widersprüchliches) Beispiel zu sein: bytes.com/topic/python/answers/… init
mlvljr
"optional", da Sie es nicht aufrufen müssen, aber wenn Sie es nicht aufrufen, wird es nicht automatisch aufgerufen.
McKay
140

Wenn Sie __init__zusätzlich zu dem, was in der aktuellen Klasse getan wird, etwas von Super's tun __init__,müssen, müssen Sie es selbst nennen, da dies nicht automatisch geschieht. Aber wenn Sie nichts von Super __init__,brauchen, müssen Sie es nicht anrufen. Beispiel:

>>> 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__ist der gleiche Weg (aber seien Sie vorsichtig, wenn Sie sich __del__für die Finalisierung darauf verlassen - erwägen Sie dies stattdessen über die with-Anweisung).

Ich benutze selten __new__. die gesamte Initialisierung in__init__.

Anon
quelle
3
Die Definition der Klasse D (C) muss so korrigiert werdensuper(D,self).__init__()
eyquem
11
super () .__ init __ () funktioniert nur in Python 3. In Python 2 benötigen Sie super (D, self) .__ init __ ()
Jacinda
"Wenn Sie etwas von Supers Init benötigen ..." - Dies ist eine sehr problematische Aussage, da es nicht darum geht, ob Sie / die Unterklasse "etwas" benötigt, sondern ob die Basisklasse etwas benötigt, um gültig zu sein Basisklasseninstanz und arbeiten korrekt. Als Implementierer der abgeleiteten Klasse sind Interna der Basisklasse Dinge, die Sie nicht wissen können / sollten, und selbst wenn Sie dies tun, weil Sie beide geschrieben haben oder Interna dokumentiert sind, kann sich das Design der Basis in Zukunft ändern und aufgrund einer schlecht geschriebenen Klasse brechen abgeleitete Klasse. Stellen Sie daher immer sicher, dass die Basisklasse vollständig initialisiert ist.
Nick
105

In Anons Antwort:
"Wenn Sie __init__zusätzlich zu dem, was in der aktuellen Klasse getan wird, etwas von Super's tun __init__müssen, müssen Sie es selbst nennen, da dies nicht automatisch geschieht."

Es ist unglaublich: Er formuliert genau das Gegenteil des Erbprinzips.


Es ist nicht so, dass "etwas von Super __init__ (...) nicht automatisch passiert" , es ist so, dass es automatisch passieren würde, aber es passiert nicht, weil die Basisklasse ' __init__durch die Definition der abgeleiteten Klassen überschrieben wird__init__

Warum also eine abgeleitete Klasse definieren __init__, da sie überschreibt, was angestrebt wird, wenn jemand auf Vererbung zurückgreift ?

Das liegt daran, dass man etwas definieren muss, das NICHT in der Basisklasse ausgeführt wird __init__, und die einzige Möglichkeit, dies zu erreichen, besteht darin, seine Ausführung in eine abgeleitete Klassenfunktion zu setzen __init__.
Mit anderen Worten, man braucht etwas in der Basisklasse " __init__zusätzlich zu dem, was automatisch in der Basisklasse gemacht wird", __init__wenn diese nicht überschrieben würde.
NICHT das Gegenteil.


Dann besteht das Problem darin, dass die gewünschten Anweisungen, die in der Basisklasse 'vorhanden __init__sind, zum Zeitpunkt der Instanziierung nicht mehr aktiviert werden. Um diese Inaktivierung auszugleichen, ist etwas Besonderes erforderlich: explizites Aufrufen der Basisklasse ' __init__, um die von der Basisklasse durchgeführte Initialisierung beizubehalten , NICHT ZU HINZUFÜGEN' __init__. Genau das steht im offiziellen Dokument:

Eine überschreibende Methode in einer abgeleiteten Klasse möchte möglicherweise die gleichnamige Basisklassenmethode erweitern, anstatt sie einfach zu ersetzen . Es gibt eine einfache Möglichkeit, die Basisklassenmethode direkt aufzurufen: Rufen Sie einfach BaseClassName.methodname (self, arguments) auf.
http://docs.python.org/tutorial/classes.html#inheritance

Das ist die ganze Geschichte:

  • Wenn das Ziel darin besteht, die von der Basisklasse durchgeführte Initialisierung, dh die reine Vererbung, beizubehalten, ist nichts Besonderes erforderlich. Es muss lediglich vermieden werden, eine __init__Funktion in der abgeleiteten Klasse zu definieren

  • Wenn das Ziel darin besteht, die von der Basisklasse durchgeführte Initialisierung zu ERSETZEN, __init__muss sie in der abgeleiteten Klasse definiert werden

  • Wenn das Ziel darin besteht, Prozesse zu der von der Basisklasse durchgeführten Initialisierung hinzuzufügen, muss eine abgeleitete Klasse __init__ definiert werden, die einen expliziten Aufruf der Basisklasse umfasst__init__


Was ich an dem Posten von Anon erstaunlich finde, ist nicht nur, dass er das Gegenteil der Vererbungstheorie zum Ausdruck bringt, sondern dass 5 Leute an diesem Upvoted vorbeigegangen sind, ohne ein Haar zu drehen, und außerdem gab es in 2 Jahren niemanden, der darauf reagierte ein Thread, dessen interessantes Thema relativ oft gelesen werden muss.

eyquem
quelle
1
Ich war mir sicher, dass dieser Beitrag positiv bewertet werden würde. Ich fürchte, ich werde nicht viele Erklärungen über die Gründe dafür haben. Es ist einfacher, herabzustimmen, als einen Text zu analysieren, der unverständlich erscheint. Ich hatte lange Zeit versucht, den Beitrag des Anon zu verstehen, bevor mir endlich klar wurde, dass er speziell geschrieben und nicht sehr maßgeblich war. Vielleicht kann es als ungefähr richtig für jemanden interpretiert werden, der über die Vererbung Bescheid weiß; aber ich finde es verwirrend, wenn ich von jemandem gelesen werde, der unsichere Vorstellungen von Vererbung hat, ein Thema, das nicht so klar ist wie
Steinwasser
2
"Ich war mir sicher, dass dieser Beitrag positiv bewertet werden würde ..." Das Hauptproblem ist, dass Sie ein wenig zu spät zur Party kommen und die meisten Leute möglicherweise nicht über die ersten Antworten hinaus lesen. Tolle Erklärung übrigens +1
Gerrat
Tolle Antwort das ist.
Trilarion
3
Sie haben die wichtigen Wörter "zusätzlich" in dem Satz, den Sie aus Aaron zitiert haben, verpasst. Aarons Aussage ist völlig richtig und stimmt mit dem überein, was Sie am Ende sagen.
GreenAsJade
1
Dies ist die erste Erklärung, die die Wahl des Python-Designs sinnvoll machte.
Joseph Garvin
20

Bearbeiten : (nach der Codeänderung)
Wir können Ihnen nicht sagen, ob Sie die Ihrer Eltern __init__(oder eine andere Funktion) aufrufen müssen oder nicht . Vererbung würde offensichtlich ohne einen solchen Anruf funktionieren. Es hängt alles von der Logik Ihres Codes ab: Wenn Sie __init__beispielsweise alles in der übergeordneten Klasse erledigen, können Sie die untergeordnete Klasse einfach ganz überspringen __init__.

Betrachten Sie das folgende Beispiel:

>>> 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
SilentGhost
quelle
Ich habe den Superaufruf aus meinem Beispiel entfernt. Ich wollte wissen, ob man die Implementierung von init durch die übergeordnete Klasse aufrufen soll oder nicht.
Georg Schölly
Vielleicht möchten Sie dann den Titel bearbeiten. aber meine Antwort steht noch.
SilentGhost
5

Es gibt keine feste Regel. In der Dokumentation für eine Klasse sollte angegeben werden, ob Unterklassen die Oberklassenmethode aufrufen sollen. Manchmal möchten Sie das Verhalten von Oberklassen vollständig ersetzen und zu anderen Zeiten erweitern - dh Ihren eigenen Code vor und / oder nach einem Aufruf von Oberklassen aufrufen.

Update: Die gleiche Grundlogik gilt für jeden Methodenaufruf. Konstruktoren müssen manchmal besonders berücksichtigt werden (da sie häufig einen verhaltensbestimmenden Zustand einrichten) und Destruktoren, da sie parallele Konstruktoren sind (z. B. bei der Zuweisung von Ressourcen, z. B. Datenbankverbindungen). Das Gleiche gilt beispielsweise beispielsweise für die render()Methode eines Widgets.

Weiteres Update: Was ist das OPP? Meinst du OOP? Nein - eine Unterklasse muss oft etwas über das Design der Oberklasse wissen . Nicht die internen Implementierungsdetails - sondern der Grundvertrag, den die Oberklasse mit ihren Kunden hat (unter Verwendung von Klassen). Dies verstößt in keiner Weise gegen die OOP-Prinzipien. Deshalb protectedist ein gültiges Konzept in OOP im Allgemeinen (obwohl natürlich nicht in Python).

Vinay Sajip
quelle
Sie sagten, dass man manchmal vor dem Aufruf der Oberklasse einen eigenen Code aufrufen möchte. Dazu benötigt man Kenntnisse über die Implementierung der Elternklasse, die gegen das OPP verstoßen würde.
Georg Schölly
4

IMO, du solltest es nennen. Wenn Ihre Superklasse ist object, sollten Sie nicht, aber in anderen Fällen denke ich, dass es außergewöhnlich ist, es nicht zu nennen. Wie bereits von anderen beantwortet, ist es sehr praktisch, wenn Ihre Klasse sich nicht einmal selbst überschreiben __init__muss, beispielsweise wenn sie keinen (zusätzlichen) internen Status zum Initialisieren hat.

u0b34a0f6ae
quelle
2

Ja, Sie sollten die Basisklasse immer __init__explizit als gute Codierungspraxis aufrufen . Wenn Sie dies vergessen, kann dies zu subtilen Problemen oder Laufzeitfehlern führen. Dies gilt auch dann, wenn __init__keine Parameter verwendet werden. Dies ist anders als in anderen Sprachen, in denen der Compiler implizit den Basisklassenkonstruktor für Sie aufruft. Python macht das nicht!

Der Hauptgrund für den ständigen Aufruf der Basisklasse _init__besteht darin, dass die Basisklasse normalerweise eine Mitgliedsvariable erstellt und diese auf Standardwerte initialisiert. Wenn Sie also nicht die Basisklasse init aufrufen, wird kein Code ausgeführt, und Sie erhalten eine Basisklasse ohne Mitgliedsvariablen.

Beispiel :

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()

Dies druckt ..

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

Führen Sie diesen Code aus .

Shital Shah
quelle