Warum werden Superklassen __init__ Methoden nicht automatisch aufgerufen?

Warum haben die Python-Designer entschieden, dass die __init__() -Methoden der __init__() die __init__() -Methoden ihrer Superklassen nicht automatisch aufrufen, wie in einigen anderen Sprachen? Ist das Pythonic und empfohlene Idiom wirklich wie folgt?

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

   

    Der entscheidende Unterschied zwischen Pythons __init__ und diesen anderen Sprachkonstruktoren besteht darin, dass __init__ kein Konstruktor ist: es ist ein Initialisierer (der eigentliche Konstruktor (falls vorhanden, aber später 😉 ist __new__ und funktioniert wieder ganz anders). Beim Konstruieren aller Superklassen (und zweifellos auch “bevor” Sie weiter nach unten bauen) gehört natürlich zu der Aussage, dass Sie die Instanz einer Unterklasse konstruieren , was für die Initialisierung eindeutig nicht der Fall ist, da es viele Anwendungsfälle gibt Die Initialisierung der Oberklassen muss übersprungen, geändert, gesteuert werden – wenn überhaupt, “in der Mitte” der Unterklasseninitialisierung und so weiter.

    Im Prinzip ist die Superklassen-Delegierung des Initialisierers in Python aus genau den gleichen Gründen nicht automatisch. Eine solche Delegierung ist auch für andere Methoden nicht automatisch – und beachten Sie, dass diese “anderen Sprachen” keine automatische Super-classn-Delegierung für irgendwelche durchführen andere Methode entweder … nur für den Konstruktor (und gegebenenfalls für den Destruktor), was, wie ich bereits erwähnte, nicht das ist , was Pythons __init__ ist. (Das Verhalten von __new__ ist auch ziemlich eigenartig, obwohl es wirklich nicht direkt mit Ihrer Frage zusammenhängt, da __new__ ein so eigentümlicher Konstruktor ist, dass es eigentlich gar nichts zu konstruieren braucht – könnte eine existierende Instanz oder sogar ein non zurückgeben -Instanz … klar, Python bietet Ihnen viel mehr Kontrolle über die Mechanik als die “anderen Sprachen”, die Sie im Sinn haben, was auch beinhaltet, keine automatische Delegierung in __new__ selbst zu haben! -).

    Es ist mir etwas peinlich, wenn Leute das “Zen von Python” plappern, als wäre es eine Rechtfertigung für irgendetwas. Es ist eine Designphilosophie; bestimmte Designentscheidungen können immer spezifischer erklärt werden – und sie müssen es sein, sonst wird das “Zen of Python” zur Entschuldigung, irgendetwas zu tun.

    Der Grund ist einfach: Sie müssen eine abgeleitete class nicht unbedingt so konstruieren, wie Sie die Basisklasse konstruieren. Sie haben möglicherweise mehr Parameter, weniger, sie können in einer anderen Reihenfolge oder überhaupt nicht verwandt sein.

     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 für alle abgeleiteten Methoden, nicht nur für __init__ .

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

    Wenn Sie eine class BaseClass mit einem Member field1 haben und eine neue SubClass class SubClass , die ein Member field2 SubClass enthält eine Instanz von SubClass Platz für field1 und field2 . Sie benötigen einen Konstruktor von BaseClass , um field1 , es sei denn, Sie benötigen alle erbenden classn, um die Initialisierung von BaseClass in ihren eigenen Konstruktoren zu wiederholen. Und wenn field1 privat ist, kann erbende classn nicht initialisieren field1 .

    Python ist nicht Java oder C ++. Alle Instanzen aller benutzerdefinierten classn haben dieselbe “Form”. Sie sind im Grunde nur Wörterbücher, in denen Attribute eingefügt werden können. Vor jeder Initialisierung sind alle Instanzen aller benutzerdefinierten classn fast identisch . Sie sind nur Orte zum Speichern von Attributen, die noch keine speichern.

    Es macht also durchaus Sinn, dass eine Python-Unterklasse ihren Basisklassenkonstruktor nicht aufruft. 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 class in der Hierarchie reserviert, und es gibt keinen Unterschied zwischen einem Attribut, das von Code aus einer BaseClass Methode hinzugefügt wird, und einem Attribut, das von Code aus einer SubClass Methode SubClass wird.

    Wenn es üblich ist, dass SubClass tatsächlich alle Invarianten von BaseClass einrichten möchte, bevor es seine eigene Anpassung durchführt, dann können Sie einfach BaseClass.__init__() aufrufen (oder super , aber das ist kompliziert und hat manchmal seine eigenen Probleme). Aber das musst du nicht. Und Sie können es vor oder nach oder mit anderen Argumenten tun. Hölle, wenn Sie wollten, könnten Sie die BaseClass.__init__ von einer anderen Methode als __init__ ; vielleicht hast du eine bizarre faule Initialisierungs-Sache.

    Python erreicht diese Flexibilität, indem es die Dinge einfach hält. Sie initialisieren Objekte, indem Sie eine __init__ Methode schreiben, die Attribute auf self . Das ist es. Es verhält sich genau wie eine Methode, weil es genau eine Methode ist. Es gibt keine anderen merkwürdigen und nicht intuitiven Regeln über Dinge, die zuerst getan werden müssen, oder Dinge, die automatisch passieren, wenn du keine anderen Dinge tust. Der einzige Zweck, den es erfüllen muss, ist es, ein Hook zu sein, der während der Initialisierung des Objekts ausgeführt wird, um initiale Attributwerte zu setzen, und genau das tut es. Wenn Sie etwas anderes machen wollen, schreiben Sie das explizit in Ihren Code.

    “Explizit ist besser als implizit.” Es ist die gleiche Argumentation, die darauf hinweist, dass wir explizit “selbst” schreiben sollten.

    Ich denke, am Ende ist es ein Vorteil – können Sie alle Regeln rezitieren, die Java zum Aufruf von Superklassen-Konstruktoren hat?

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

    Im Moment haben wir eine ziemlich lange Seite, die die Reihenfolge der Methodenauflösung bei Mehrfachvererbung beschreibt: http://www.python.org/download/releases/2.3/mro/

    Wenn Konstruktoren automatisch aufgerufen werden, benötigen Sie eine weitere Seite, die mindestens die gleiche Länge hat und die Reihenfolge der Ereignisse erläutert. Das wäre die Hölle …

    Vielleicht ist __init__ die Methode, die die Unterklasse überschreiben muss. Manchmal müssen Unterklassen die function des übergeordneten Elements ausführen, bevor sie klassenspezifischen Code hinzufügen, und manchmal müssen sie Instanzvariablen einrichten, bevor sie die function des übergeordneten Elements aufrufen. Da Python unmöglich wissen kann, wann es am besten wäre, diese functionen aufzurufen, sollte es nicht raten.

    Wenn diese dich nicht beeinflussen, __init__ , dass __init__ nur eine andere function ist. Wenn die fragliche function dostuff wäre, möchten Sie trotzdem, dass Python automatisch die entsprechende function in der dostuff ?

    Ich glaube, die eine sehr wichtige Überlegung hier ist, dass Sie mit einem automatischen Aufruf von super.__init__() , per Entwurf, super.__init__() , wann diese Initialisierungsmethode aufgerufen wird und mit welchen Argumenten. Wenn man es nicht automatisch aufrufen möchte und der Programmierer diesen Aufruf explizit ausführen muss, bringt das viel Flexibilität mit sich.

    denn nur weil class B von class A abgeleitet ist, heißt das nicht, dass A.__init__() mit denselben Argumenten wie B.__init__() aufgerufen werden kann oder soll. B.__init__() der Aufruf explizit ist, bedeutet dies, dass ein Programmierer beispielsweise B.__init__() mit völlig anderen Parametern definieren, eine Berechnung mit diesen Daten durchführen, A.__init__() mit den für diese Methode geeigneten Argumenten aufrufen und dann nachbearbeiten kann. Diese Art von Flexibilität wäre schwierig zu erreichen, wenn A.__init__() implizit von B.__init__() aufgerufen würde, entweder bevor B.__init__() ausgeführt wird oder direkt danach.

    Um Verwirrung zu vermeiden, ist es nützlich zu wissen, dass Sie die Methode base_class __init__() aufrufen können, wenn die class child keine class __init__() .

    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 pa # prints 5 print qb # prints 0 print za # prints 1 

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

    Zum Beispiel gibt der folgende Code einen Fehler zurück: class parent: def init (selbst, 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 za # Error: No attribute named as a can be found.