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'
python
inheritance
subclass
delegation
superclass
jrdioko
quelle
quelle
__init__
Methode zu erben , und vielleicht sogar automatisch nach Unterklassen suchen und diese dekorieren.Antworten:
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! -).quelle
__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!__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.__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.__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.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.
Dies gilt nicht nur für alle abgeleiteten Methoden
__init__
.quelle
Java und C ++ erfordern, dass ein Basisklassenkonstruktor aufgrund des Speicherlayouts aufgerufen wird.
Wenn Sie eine Klasse
BaseClass
mit einem Mitgliedfield1
haben und eine neue Klasse erstellenSubClass
, die ein Mitglied hinzufügtfield2
,SubClass
enthält eine Instanz von Platz fürfield1
undfield2
.BaseClass
Zum Ausfüllen benötigen Sie einen Konstruktor vonfield1
, es sei denn, Sie benötigen alle ererbenden Klassen, umBaseClass
die Initialisierung in ihren eigenen Konstruktoren zu wiederholen . Und wennfield1
es privat ist, kann das Erben von Klassen nicht initialisiert werdenfield1
.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
BaseClass
Methode hinzugefügt wurde, und einem Attribut, das durch Code aus einerSubClass
Methode hinzugefügt wurde .Wenn, wie üblich,
SubClass
tatsächlich alleBaseClass
Invarianten eingerichtet werden sollen, bevor die eigene Anpassung vorgenommen wird, können Sie diese einfach aufrufenBaseClass.__init__()
(oder verwendensuper
, 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 dasBaseClass.__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 festlegtself
. 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.quelle
"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?
quelle
Oft hat die Unterklasse zusätzliche Parameter, die nicht an die Oberklasse übergeben werden können.
quelle
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 ...
quelle
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:
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
quelle
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 Funktiondostuff
stattdessen wäre, möchten Sie dennoch, dass Python die entsprechende Funktion in der übergeordneten Klasse automatisch aufruft?quelle
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 sollteB.__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, wennA.__init__()
sieB.__init__()
implizit aufgerufen würde , entweder vor derB.__init__()
Ausführung oder direkt danach.quelle