Angenommen, ich habe ein Szenario mit Mehrfachvererbung:
class A(object):
# code for A here
class B(object):
# code for B here
class C(A, B):
def __init__(self):
# What's the right code to write here to ensure
# A.__init__ and B.__init__ get called?
Es gibt zwei typische Ansätze zum Schreiben C
‚s __init__
:
- (alter Stil)
ParentClass.__init__(self)
- (neuerer Stil)
super(DerivedClass, self).__init__()
In beiden Fällen funktioniert der Code jedoch nicht richtig , wenn die übergeordneten Klassen ( A
und B
) nicht derselben Konvention folgen (einige werden möglicherweise übersehen oder mehrmals aufgerufen).
Also, was ist wieder der richtige Weg? Es ist leicht zu sagen, "sei einfach konsequent, folge dem einen oder anderen", aber wenn A
oder B
aus einer Bibliothek eines Drittanbieters, was dann? Gibt es einen Ansatz, der sicherstellen kann, dass alle übergeordneten Klassenkonstruktoren aufgerufen werden (und zwar in der richtigen Reihenfolge und nur einmal)?
Bearbeiten: um zu sehen, was ich meine, wenn ich tue:
class A(object):
def __init__(self):
print("Entering A")
super(A, self).__init__()
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
A.__init__(self)
B.__init__(self)
print("Leaving C")
Dann bekomme ich:
Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C
Beachten Sie, dass B
der Init zweimal aufgerufen wird. Wenn ich mache:
class A(object):
def __init__(self):
print("Entering A")
print("Leaving A")
class B(object):
def __init__(self):
print("Entering B")
super(B, self).__init__()
print("Leaving B")
class C(A, B):
def __init__(self):
print("Entering C")
super(C, self).__init__()
print("Leaving C")
Dann bekomme ich:
Entering C
Entering A
Leaving A
Leaving C
Beachten Sie, dass B
der Init nie aufgerufen wird. Es scheint also, dass ich keine sichere Wahl für die Klasse treffen kann, die ich schreibe ( ) , wenn ich nicht die Initialen der Klassen kenne / kontrolliere, von denen ich erbe ( A
und ).B
C
quelle
Antworten:
Beide Wege funktionieren gut. Der verwendete Ansatz
super()
führt zu einer größeren Flexibilität für Unterklassen.Im Direktanruf-Ansatz
C.__init__
können sowohlA.__init__
als auch angerufen werdenB.__init__
.Bei der Verwendung
super()
müssen die Klassen für eine kooperative Mehrfachvererbung ausgelegt sein, bei derC
Aufrufesuper
denA
Code aufrufen,super
der auch denB
Code aufruft . Weitere Informationen dazu, was getan werden kann, finden Sie unter http://rhettinger.wordpress.com/2011/05/26/super-considered-supersuper
.[Antwortfrage wie später bearbeitet]
Der Artikel, auf den verwiesen wird, zeigt, wie Sie mit dieser Situation umgehen können, indem Sie eine Wrapper-Klasse um
A
und hinzufügenB
. Im Abschnitt "Einbindung einer nicht kooperativen Klasse" finden Sie ein ausgearbeitetes Beispiel.Man könnte sich wünschen, dass die Mehrfachvererbung einfacher wäre und Sie mühelos Auto- und Flugzeugklassen zusammenstellen könnten, um ein FlyingCar zu erhalten. In der Realität benötigen jedoch separat gestaltete Komponenten häufig Adapter oder Wrapper, bevor sie so nahtlos zusammenpassen, wie wir möchten :-)
Ein anderer Gedanke: Wenn Sie mit dem Erstellen von Funktionen mit Mehrfachvererbung nicht zufrieden sind, können Sie die Komposition verwenden, um vollständig zu steuern, welche Methoden bei welchen Gelegenheiten aufgerufen werden.
quelle
super().__init__()
Ansatz machen. Wenn ich anrufenA.__init__()
undB.__init__()
direkt, dann (wenn A und B Telefonierensuper
) erhalte ich B init mehrmals aufgerufen werden.Die Antwort auf Ihre Frage hängt von einem sehr wichtigen Aspekt ab: Sind Ihre Basisklassen für Mehrfachvererbung ausgelegt?
Es gibt 3 verschiedene Szenarien:
Die Basisklassen sind unabhängige, eigenständige Klassen.
Wenn Ihre Basisklassen separate Entitäten sind, die unabhängig voneinander funktionieren können und sich nicht kennen, sind sie nicht für die Mehrfachvererbung ausgelegt. Beispiel:
Wichtig: Beachten Sie, dass weder
Foo
nochBar
Anrufesuper().__init__()
! Aus diesem Grund hat Ihr Code nicht richtig funktioniert. Aufgrund der Funktionsweise der Diamantvererbung in Python sollten Klassen, deren Basisklasse ist,object
nicht aufgerufen werdensuper().__init__()
. Wie Sie bemerkt haben, würde dies die Mehrfachvererbung unterbrechen, da Sie am Ende__init__
eher eine andere Klasse als eine anrufenobject.__init__()
. ( Haftungsausschluss: Das Vermeidensuper().__init__()
vonobject
Unterklassen ist meine persönliche Empfehlung und keineswegs ein vereinbarter Konsens in der Python-Community. Einige Leute bevorzugen die Verwendungsuper
in jeder Klasse und argumentieren, dass Sie immer einen Adapter schreiben können, wenn sich die Klasse nicht so verhält du erwartest.)Dies bedeutet auch, dass Sie niemals eine Klasse schreiben sollten, die von
object
einer__init__
Methode erbt und keine hat . Das Nichtdefinieren einer__init__
Methode hat den gleichen Effekt wie das Aufrufensuper().__init__()
. Wenn Ihre Klasse direkt von erbtobject
, stellen Sie sicher, dass Sie einen leeren Konstruktor wie folgt hinzufügen:In dieser Situation müssen Sie jeden übergeordneten Konstruktor manuell aufrufen. Es gibt zwei Möglichkeiten, dies zu tun:
Ohne
super
Mit
super
Jede dieser beiden Methoden hat ihre eigenen Vor- und Nachteile. Wenn Sie verwenden
super
, unterstützt Ihre Klasse die Abhängigkeitsinjektion . Andererseits ist es einfacher, Fehler zu machen. Wenn Sie beispielsweise die Reihenfolge vonFoo
undBar
(wieclass FooBar(Bar, Foo)
) ändern , müssen Sie diesuper
Anrufe entsprechend aktualisieren . Ohne dasssuper
Sie sich darüber keine Sorgen machen müssen und der Code viel besser lesbar ist.Eine der Klassen ist ein Mixin.
Ein Mixin ist eine Klasse, die entworfen wurde , mit Mehrfachvererbung verwendet werden soll. Dies bedeutet, dass wir nicht beide übergeordneten Konstruktoren manuell aufrufen müssen, da das Mixin automatisch den 2. Konstruktor für uns aufruft. Da wir diesmal nur einen einzigen Konstruktor aufrufen müssen, können wir dies tun, um
super
zu vermeiden, dass der Name der übergeordneten Klasse fest codiert werden muss.Beispiel:
Die wichtigen Details hier sind:
super().__init__()
alle empfangenen Argumente auf und durchläuft sie.class FooBar(FooMixin, Bar)
. Wenn die Reihenfolge der Basisklassen falsch ist, wird der Konstruktor des Mixins niemals aufgerufen.Alle Basisklassen sind für die kooperative Vererbung ausgelegt.
Klassen, die für die kooperative Vererbung entwickelt wurden, ähneln Mixins: Sie leiten alle nicht verwendeten Argumente an die nächste Klasse weiter. Wie zuvor müssen wir nur anrufen
super().__init__()
und alle übergeordneten Konstruktoren werden als Kettenaufruf bezeichnet.Beispiel:
In diesem Fall spielt die Reihenfolge der übergeordneten Klassen keine Rolle. Wir könnten genauso gut von erben
CoopBar
zuerst , und der Code würde immer noch genauso funktionieren. Dies gilt jedoch nur, da alle Argumente als Schlüsselwortargumente übergeben werden. Die Verwendung von Positionsargumenten würde es leicht machen, die Reihenfolge der Argumente falsch zu bestimmen. Daher ist es üblich, dass kooperative Klassen nur Schlüsselwortargumente akzeptieren.Dies ist auch eine Ausnahme von der Regel, die ich zuvor erwähnt habe: Beide
CoopFoo
undCoopBar
erben vonobject
, aber sie rufen immer noch aufsuper().__init__()
. Andernfalls würde es keine kooperative Vererbung geben.Fazit: Die richtige Implementierung hängt von den Klassen ab, von denen Sie erben.
Der Konstruktor ist Teil der öffentlichen Schnittstelle einer Klasse. Wenn die Klasse als Mixin oder zur kooperativen Vererbung konzipiert ist, muss dies dokumentiert werden. Wenn in den Dokumenten nichts dergleichen erwähnt wird, kann davon ausgegangen werden, dass die Klasse nicht für die kooperative Mehrfachvererbung ausgelegt ist.
quelle
super().__init__(*args, **kwargs)
in das Mixin aufzunehmen und es zuerst zu schreiben. Es macht so viel Sinn.Jeder Ansatz ("neuer Stil" oder "alter Stil") funktioniert, wenn Sie die Kontrolle über den Quellcode für
A
und habenB
. Andernfalls kann die Verwendung einer Adapterklasse erforderlich sein.Quellcode zugänglich: Richtige Verwendung des "neuen Stils"
Hier bestimmt die Methodenauflösungsreihenfolge (MRO) Folgendes:
C(A, B)
diktiertA
dann zuerstB
. MRO istC -> A -> B -> object
.super(A, self).__init__()
weiter entlang der MRO - Kette in eingeleitetC.__init__
zuB.__init__
.super(B, self).__init__()
weiter entlang der MRO - Kette in eingeleitetC.__init__
zuobject.__init__
.Man könnte sagen, dass dieser Fall für die Mehrfachvererbung ausgelegt ist .
Quellcode zugänglich: Richtige Verwendung des "alten Stils"
Hier spielt MRO keine Rolle, da
A.__init__
undB.__init__
explizit aufgerufen werden.class C(B, A):
würde genauso gut funktionieren.Obwohl dieser Fall im neuen Stil nicht wie der vorherige für die Mehrfachvererbung "ausgelegt" ist, ist eine Mehrfachvererbung weiterhin möglich.
Was ist nun, wenn
A
undB
aus einer Drittanbieter-Bibliothek stammen - dh Sie haben keine Kontrolle über den Quellcode fürA
undB
? Die kurze Antwort: Sie müssen eine Adapterklasse entwerfen, die die erforderlichensuper
Aufrufe implementiert , und dann eine leere Klasse verwenden, um die MRO zu definieren (siehe Raymond Hettingers Artikel übersuper
- insbesondere den Abschnitt "Einbinden einer nicht kooperativen Klasse").Eltern von Drittanbietern:
A
nicht implementiertsuper
;B
tutKlasse
Adapter
implementiert,super
so dassC
die MRO definiert werden kann, die bei dersuper(Adapter, self).__init__()
Ausführung ins Spiel kommt.Und wenn es umgekehrt ist?
Eltern von Drittanbietern:
A
Gerätesuper
;B
nichtGleiches Muster hier, außer dass die Ausführungsreihenfolge eingeschaltet ist
Adapter.__init__
;super
zuerst anrufen, dann explizit anrufen. Beachten Sie, dass für jeden Fall mit Eltern von Drittanbietern eine eindeutige Adapterklasse erforderlich ist.Obwohl Sie die Fälle behandeln können, in denen Sie den Quellcode von und mithilfe einer Adapterklasse nicht steuern , müssen Sie wissen, wie die Inits der übergeordneten Klassen (wenn überhaupt) implementiert werden, um dies zu tun.
A
B
super
quelle
Wie Raymond in seiner Antwort sagte, ein direkter Anruf bei
A.__init__
undB.__init__
funktioniert gut, und Ihr Code wäre lesbar.Die Vererbungsverknüpfung zwischen
C
und diesen Klassen wird jedoch nicht verwendet . Wenn Sie diesen Link nutzen, erhalten Sie mehr Konsistenz und können eventuelle Refactorings einfacher und weniger fehleranfällig durchführen. Ein Beispiel dafür:quelle
Dieser Artikel erklärt die kooperative Mehrfachvererbung:
http://www.artima.com/weblogs/viewpost.jsp?thread=281127
Es wird die nützliche Methode erwähnt, die
mro()
Ihnen die Reihenfolge der Methodenauflösung zeigt. In Ihrem zweiten Beispiel, wo Sie rufensuper
inA
dersuper
weiterhin Anruf in MRO. Die nächste Klasse in der Reihenfolge istB
, deshalbB
der Init zum ersten Mal aufgerufen wird.Hier ist ein technischerer Artikel von der offiziellen Python-Site:
http://www.python.org/download/releases/2.3/mro/
quelle
Wenn Sie Unterklassenklassen aus Bibliotheken von Drittanbietern multiplizieren, gibt es keinen blinden Ansatz zum Aufrufen der Basisklassenmethoden
__init__
(oder anderer Methoden), der tatsächlich funktioniert, unabhängig davon, wie die Basisklassen programmiert sind.super
macht es möglich , zu Schreibklassen entwickelt , um gemeinsam Methoden im Rahmen von komplexen Mehrfachvererbung Bäume zu implementieren , die zu der Klasse Autor muss nicht bekannt sein. Es gibt jedoch keine Möglichkeit, damit korrekt von beliebigen Klassen zu erben, die möglicherweise verwendet werden oder nichtsuper
.Ob eine Klasse mit
super
oder mit direkten Aufrufen der Basisklasse für eine Unterklasse ausgelegt ist, ist im Wesentlichen eine Eigenschaft, die Teil der "öffentlichen Schnittstelle" der Klasse ist und als solche dokumentiert werden sollte. Wenn Sie Bibliotheken von Drittanbietern so verwenden, wie es der Bibliotheksautor erwartet hat, und die Bibliothek über eine angemessene Dokumentation verfügt, werden Sie normalerweise darüber informiert, was Sie tun müssen, um bestimmte Dinge in Unterklassen zu unterteilen. Wenn nicht, müssen Sie sich den Quellcode der Klassen ansehen, die Sie unterklassifizieren, und herausfinden, wie ihre Konvention zum Aufrufen von Basisklassen lautet. Wenn Sie mehrere Klassen aus einer oder mehreren Bibliotheken von Drittanbietern auf eine Weise kombinieren, die die Autoren der Bibliothek nicht erwartet hatten, ist es möglicherweise nicht möglich, Methoden der Superklasse konsistent aufzurufen Superklasse überhaupt;; Wenn Klasse A Teil einer Hierarchie ist, die verwendet,super
und Klasse B Teil einer Hierarchie ist, die Super nicht verwendet, funktioniert garantiert keine der beiden Optionen. Sie müssen eine Strategie finden, die für jeden Einzelfall funktioniert.quelle