Warum werden Methoden der Superklasse __init__ nicht automatisch aufgerufen?

155

Warum haben die Python-Designer entschieden, dass __init__()die __init__()Methoden von Unterklassen die Methoden ihrer Oberklassen nicht automatisch aufrufen , wie in einigen anderen Sprachen? Entspricht die pythonische und empfohlene Redewendung wirklich der folgenden?

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

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'
jrdioko
quelle
2
Sie können einen Dekorator schreiben, um die __init__Methode zu erben , und vielleicht sogar automatisch nach Unterklassen suchen und diese dekorieren.
osa
2
@osa, klingt eine wirklich gute Idee. Möchten Sie etwas mehr über den Dekorateur-Teil beschreiben?
Diansheng
1
@osa ja mehr beschreiben!
Charlie Parker

Antworten:

162

Der entscheidende Unterschied zwischen Python __init__und den anderen Sprachen Konstrukteuren ist , dass __init__ist nicht ein Konstruktor: es ist ein Initialisierer (die tatsächliche Konstruktor (falls vorhanden, aber finden Sie weiter unten ;-) ist __new__und funktioniert ganz anders wieder). Während der Konstruktion alle übergeordneten Klassen (und ohne Zweifel dabei „vor“ Sie weiterhin den Bau nach unten) sind offensichtlich Teil zu sagen ist , dass Sie den Bau einer Unterklasse Instanz, das ist eindeutig nicht der Fall für die Initialisierung, da es viele Anwendungsfälle gibt, in denen die Initialisierung von Oberklassen übersprungen, geändert, kontrolliert werden muss - wenn überhaupt "in der Mitte" der Initialisierung von Unterklassen und so weiter.

Grundsätzlich super-Klasse Delegation der Initialisierer ist nicht automatisch in Python für genau den gleichen Gründen eine solche Übertragung auch nicht automatisch allen anderen Methoden - und beachten Sie, dass diese „andere Sprachen“ nicht tun automatische Super-Klasse - Delegation für jede andere Methode entweder ... nur für den Konstruktor (und gegebenenfalls den Destruktor), was, wie ich bereits erwähnte, nicht das ist , was Python __init__ist. (Das Verhalten von __new__ist auch ziemlich eigenartig, obwohl es wirklich nicht direkt mit Ihrer Frage zusammenhängt, da __new__es ein so eigenartiger Konstruktor ist, dass es nicht unbedingt irgendetwas konstruieren muss - könnte durchaus eine vorhandene Instanz oder sogar eine Nichtinstanz zurückgeben ... Python bietet Ihnen eindeutig vielmehr Kontrolle über die Mechanik als die "anderen Sprachen", an die Sie denken, was auch beinhaltet, dass keine automatische Delegierung an __new__sich vorhanden ist! -).

Alex Martelli
quelle
7
Upvoted, weil für mich der eigentliche Vorteil die Fähigkeit ist, die Sie erwähnen, um zu jedem Zeitpunkt in der Initialisierung der Unterklasse (oder überhaupt nicht) _ init () _ der Oberklasse aufzurufen .
Kindall
53
-1 für " __init__ist kein Konstruktor ... der eigentliche Konstruktor ... ist __new__". Wie Sie selbst bemerken, __new__verhält sich nichts wie ein Konstruktor aus anderen Sprachen. __init__ist in der Tat sehr ähnlich (es wird während der Erstellung eines neuen Objekts aufgerufen, nachdem dieses Objekt zugewiesen wurde, um Mitgliedsvariablen für das neue Objekt festzulegen) und ist fast immer der Ort, an dem Funktionen implementiert werden, die Sie in anderen Sprachen eingeben würden ein Konstruktor. Nennen Sie es einfach einen Konstruktor!
Ben
8
Ich denke auch, dass dies eine ziemlich absurde Aussage ist: "Das Konstruieren aller Oberklassen ... ist offensichtlich ein Teil der Aussage, dass Sie die Instanz einer Unterklasse konstruieren , was bei der Initialisierung eindeutig nicht der Fall ist. " Es gibt nichts in den Worten Konstruktion / Initialisierung, was dies für irgendjemanden "offensichtlich" macht. Und __new__ruft auch nicht automatisch die Superklasse auf __new__. Ihre Behauptung, dass der Hauptunterschied darin besteht, dass die Konstruktion notwendigerweise die Konstruktion von Oberklassen beinhaltet, während die Initialisierung nicht mit Ihrer Behauptung, __new__die der Konstruktor ist, unvereinbar ist.
Ben
36
In den Python-Dokumenten für __init__unter docs.python.org/reference/datamodel.html#basic-customization heißt es : "Als besondere Einschränkung für Konstruktoren kann kein Wert zurückgegeben werden. Dadurch wird zur Laufzeit ein TypeError ausgelöst "(Hervorhebung von mir). Es ist also offiziell __init__ein Konstrukteur.
Ben
3
Wird in der Python / Java-Terminologie __init__als Konstruktor bezeichnet. Dieser Konstruktor ist eine Initialisierungsfunktion, die aufgerufen wird, nachdem das Objekt vollständig erstellt und auf einen Standardzustand einschließlich seines endgültigen Laufzeittyps initialisiert wurde. Es ist nicht äquivalent zu C ++ - Konstruktoren, die für ein statisch typisiertes, zugewiesenes Objekt mit einem undefinierten Status aufgerufen werden. Diese unterscheiden sich auch von __new__, so dass wir wirklich mindestens vier verschiedene Arten von Zuordnungs- / Konstruktions- / Initialisierungsfunktionen haben. Sprachen verwenden eine gemischte Terminologie, und der wichtige Teil ist das Verhalten und nicht die Terminologie.
Elazar
36

Es ist mir etwas peinlich, wenn Leute das "Zen of Python" nachahmen, als ob es eine Rechtfertigung für irgendetwas wäre. Es ist eine Designphilosophie; Bestimmte Designentscheidungen können immer spezifischer erklärt werden - und das müssen sie auch sein, sonst wird das "Zen of Python" zu einer Ausrede für alles.

Der Grund ist einfach: Sie erstellen eine abgeleitete Klasse nicht unbedingt auf ähnliche Weise wie die Basisklasse. Möglicherweise haben Sie mehr Parameter, weniger, sie befinden sich möglicherweise in einer anderen Reihenfolge oder sind überhaupt nicht miteinander verbunden.

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

Dies gilt nicht nur für alle abgeleiteten Methoden __init__.

Glenn Maynard
quelle
6
Bietet dieses Beispiel etwas Besonderes? statische Sprachen können dies auch
Jean
18

Java und C ++ erfordern, dass ein Basisklassenkonstruktor aufgrund des Speicherlayouts aufgerufen wird.

Wenn Sie eine Klasse BaseClassmit einem Mitglied field1haben und eine neue Klasse erstellen SubClass, die ein Mitglied hinzufügt field2, SubClassenthält eine Instanz von Platz für field1und field2. BaseClassZum Ausfüllen benötigen Sie einen Konstruktor von field1, es sei denn, Sie benötigen alle ererbenden Klassen, um BaseClassdie Initialisierung in ihren eigenen Konstruktoren zu wiederholen . Und wenn field1es privat ist, kann das Erben von Klassen nicht initialisiert werden field1.

Python ist nicht Java oder C ++. Alle Instanzen aller benutzerdefinierten Klassen haben dieselbe 'Form'. Sie sind im Grunde nur Wörterbücher, in die Attribute eingefügt werden können. Bevor eine Initialisierung durchgeführt wurde, sind alle Instanzen aller benutzerdefinierten Klassen fast genau gleich . Sie sind nur Orte zum Speichern von Attributen, die noch keine gespeichert haben.

Daher ist es für eine Python-Unterklasse durchaus sinnvoll, ihren Basisklassenkonstruktor nicht aufzurufen. Es könnte einfach die Attribute selbst hinzufügen, wenn es wollte. Es ist kein Platz für eine bestimmte Anzahl von Feldern für jede Klasse in der Hierarchie reserviert, und es gibt keinen Unterschied zwischen einem Attribut, das durch Code aus einer BaseClassMethode hinzugefügt wurde, und einem Attribut, das durch Code aus einer SubClassMethode hinzugefügt wurde .

Wenn, wie üblich, SubClasstatsächlich alle BaseClassInvarianten eingerichtet werden sollen, bevor die eigene Anpassung vorgenommen wird, können Sie diese einfach aufrufen BaseClass.__init__()(oder verwenden super, aber das ist kompliziert und hat manchmal eigene Probleme). Aber das musst du nicht. Und Sie können es vorher oder nachher oder mit verschiedenen Argumenten tun. Zur Hölle, wenn Sie wollten, könnten Sie das BaseClass.__init__von einer anderen Methode ganz aufrufen als __init__; Vielleicht haben Sie eine bizarre Sache mit der faulen Initialisierung.

Python erreicht diese Flexibilität, indem es die Dinge einfach hält. Sie initialisieren Objekte, indem Sie eine __init__Methode schreiben , die Attribute festlegt self. Das ist es. Es verhält sich genau wie eine Methode, weil es genau eine Methode ist. Es gibt keine anderen seltsamen und nicht intuitiven Regeln für Dinge, die zuerst erledigt werden müssen, oder für Dinge, die automatisch passieren, wenn Sie keine anderen Dinge tun. Der einzige Zweck, dem es dienen muss, ist ein Hook, der während der Objektinitialisierung ausgeführt wird, um anfängliche Attributwerte festzulegen, und genau das tut es. Wenn Sie möchten, dass es etwas anderes tut, schreiben Sie dies explizit in Ihren Code.

Ben
quelle
2
C ++ hat nichts mit dem "Speicherlayout" zu tun. Das gleiche Initialisierungsmodell wie Python bietet möglicherweise in C ++ an. Der einzige Grund, warum Konstruktion / Zerstörung so ist, wie sie in C ++ sind, liegt in der Entwurfsentscheidung, zuverlässige und gut verhaltene Einrichtungen für das Ressourcenmanagement (RAII) bereitzustellen, die ebenso gut automatisch generiert werden können (was weniger Code und menschliche Fehler bedeutet). von Compilern, da die Regeln für sie (ihre Aufrufreihenfolge) streng definiert sind. Ich bin mir nicht sicher, aber Java folgte höchstwahrscheinlich einfach diesem Ansatz, um eine weitere POLA C-ähnliche Sprache zu sein.
Alexander Shukaev
10

"Explizit ist besser als implizit." Es ist dieselbe Argumentation, die besagt, dass wir explizit 'Selbst' schreiben sollten.

Ich denke, am Ende ist es ein Vorteil - können Sie alle Regeln rezitieren, die Java bezüglich des Aufrufs der Konstruktoren von Superklassen hat?

Mike Axiak
quelle
8
Ich stimme Ihnen größtenteils zu, aber Javas Regel ist eigentlich ziemlich einfach: Der Konstruktor ohne Argumente wird aufgerufen, es sei denn, Sie fragen ausdrücklich nach einem anderen.
Laurence Gonsalves
1
@Laurence - Was passiert, wenn die übergeordnete Klasse keinen Konstruktor ohne Argumente definiert? Was passiert, wenn der Konstruktor ohne Argumente geschützt oder privat ist?
Mike Axiak
8
Genau das gleiche, was passieren würde, wenn Sie versuchen würden, es explizit aufzurufen.
Laurence Gonsalves
8

Oft hat die Unterklasse zusätzliche Parameter, die nicht an die Oberklasse übergeben werden können.

John La Rooy
quelle
7

Derzeit haben wir eine ziemlich lange Seite, auf der die Reihenfolge der Methodenauflösung bei Mehrfachvererbung beschrieben wird: http://www.python.org/download/releases/2.3/mro/

Wenn Konstruktoren automatisch aufgerufen würden, benötigen Sie eine weitere Seite mit mindestens derselben Länge, auf der die Reihenfolge erläutert wird. Das wäre die Hölle ...

Viraptor
quelle
Das war die richtige Antwort. Python müsste die Semantik der Argumentation zwischen Unterklasse und Oberklasse definieren. Schade, dass diese Antwort nicht positiv bewertet wird. Vielleicht, wenn es das Problem mit einigen Beispielen zeigte?
Gary Weiss
5

Um Verwirrung zu vermeiden, ist es hilfreich zu wissen, dass Sie die base_class- __init__()Methode aufrufen können , wenn die child_class keine __init__()Klasse hat.

Beispiel:

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

Tatsächlich sucht das MRO in Python __init__()in der übergeordneten Klasse, wenn es in der untergeordneten Klasse nicht gefunden werden kann. Sie müssen den übergeordneten Klassenkonstruktor direkt aufrufen, wenn Sie bereits eine __init__()Methode in der untergeordneten Klasse haben.

Der folgende Code gibt beispielsweise einen Fehler zurück: 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.
Keyvanrm
quelle
3

Vielleicht __init__ist die Methode, die die Unterklasse überschreiben muss. Manchmal müssen Unterklassen die Funktion des übergeordneten Elements ausführen, bevor sie klassenspezifischen Code hinzufügen, und manchmal müssen sie Instanzvariablen einrichten, bevor sie die Funktion des übergeordneten Elements aufrufen. Da Python möglicherweise nicht wissen kann, wann es am besten geeignet ist, diese Funktionen aufzurufen, sollte es nicht raten.

Wenn diese Sie nicht beeinflussen, denken Sie, dass dies __init__nur eine weitere Funktion ist. Wenn die betreffende Funktion dostuffstattdessen wäre, möchten Sie dennoch, dass Python die entsprechende Funktion in der übergeordneten Klasse automatisch aufruft?

Kirk Strauser
quelle
2

Ich glaube, die eine sehr wichtige Überlegung hier ist, dass Sie mit einem automatischen Aufruf super.__init__()von Design aus verbieten, wann diese Initialisierungsmethode aufgerufen wird und mit welchen Argumenten. Das automatische Aufrufen zu vermeiden und vom Programmierer zu verlangen, diesen Aufruf explizit auszuführen, erfordert viel Flexibilität.

Nur weil Klasse B von Klasse A abgeleitet ist, heißt das nicht, A.__init__()dass sie mit denselben Argumenten wie aufgerufen werden kann oder sollte B.__init__(). Wenn Sie den Aufruf explizit machen, kann ein Programmierer z. B. B.__init__()mit völlig anderen Parametern definieren, einige Berechnungen mit diesen Daten durchführen, A.__init__()mit Argumenten aufrufen, die für diese Methode geeignet sind, und dann eine Nachbearbeitung durchführen. Diese Art von Flexibilität wäre umständlich zu erreichen, wenn A.__init__()sie B.__init__()implizit aufgerufen würde , entweder vor der B.__init__()Ausführung oder direkt danach.

fließen
quelle