Wie funktioniert Pythons super () mit Mehrfachvererbung?

888

Ich bin ziemlich neu in der objektorientierten Programmierung von Python und habe Probleme, die super()Funktion (neue Stilklassen) zu verstehen, insbesondere wenn es um Mehrfachvererbung geht.

Zum Beispiel, wenn Sie etwas haben wie:

class First(object):
    def __init__(self):
        print "first"

class Second(object):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print "that's it"

Was ich nicht bekomme ist: wird das Third() Klasse beide Konstruktormethoden erben? Wenn ja, welches wird dann mit super () ausgeführt und warum?

Und was ist, wenn Sie den anderen ausführen möchten? Ich weiß, dass es etwas mit der Python Method Resolution Resolution ( MRO ) zu tun hat .

Callisto
quelle
Tatsächlich ist die Mehrfachvererbung der einzige Fall, in dem dies super()von Nutzen ist. Ich würde nicht empfehlen, es mit Klassen zu verwenden, die lineare Vererbung verwenden, wo es nur nutzlosen Overhead ist.
Bachsau
9
@Bachsau ist technisch korrekt, da es sich um einen kleinen Overhead handelt, aber super () pythonischer ist und das Re-Factoring und Änderungen des Codes im Laufe der Zeit ermöglicht. Verwenden Sie super (), es sei denn, Sie benötigen wirklich eine benannte klassenspezifische Methode.
Paul Whipp
2
Ein weiteres Problem super()besteht darin, dass jede Unterklasse gezwungen wird, sie ebenfalls zu verwenden. Wenn sie nicht verwendet wird super(), kann jeder, der sie verwendet, selbst entscheiden. Wenn ein Entwickler, der es verwendet, nichts darüber weiß super()oder nicht weiß, dass es verwendet wurde, können Probleme mit dem mro auftreten, die sehr schwer zu finden sind.
Bachsau
Ich habe praktisch jede Antwort hier auf die eine oder andere Weise verwirrend gefunden. Sie würden stattdessen tatsächlich hier verweisen .
Matanster vor

Antworten:

709

Dies wird von Guido selbst in seinem Blog-Beitrag Method Resolution Order (einschließlich zweier früherer Versuche) mit einer angemessenen Menge an Details beschrieben .

In Ihrem Beispiel Third()wird anrufen First.__init__. Python sucht nach jedem Attribut in den Eltern der Klasse, da diese von links nach rechts aufgelistet sind. In diesem Fall suchen wir __init__. Also, wenn Sie definieren

class Third(First, Second):
    ...

Python beginnt mit dem Betrachten Firstund wenn Firstes das Attribut nicht hat, wird es sich ansehen Second.

Diese Situation wird komplexer, wenn die Vererbung beginnt, Pfade zu kreuzen (z. B. wenn sie Firstvererbt wird Second). Lesen Sie den obigen Link, um weitere Informationen zu erhalten. Kurz gesagt, Python versucht, die Reihenfolge beizubehalten, in der jede Klasse in der Vererbungsliste angezeigt wird, beginnend mit der untergeordneten Klasse selbst.

Also zum Beispiel, wenn Sie hatten:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First):
    def __init__(self):
        print "third"

class Fourth(Second, Third):
    def __init__(self):
        super(Fourth, self).__init__()
        print "that's it"

der MRO wäre [Fourth, Second, Third, First].

Übrigens: Wenn Python keine kohärente Reihenfolge für die Methodenauflösung findet, wird eine Ausnahme ausgelöst, anstatt auf ein Verhalten zurückzugreifen, das den Benutzer überraschen könnte.

Bearbeitet, um ein Beispiel für eine mehrdeutige MRO hinzuzufügen:

class First(object):
    def __init__(self):
        print "first"

class Second(First):
    def __init__(self):
        print "second"

class Third(First, Second):
    def __init__(self):
        print "third"

Sollte ThirdMRO sein [First, Second]oder [Second, First]? Es gibt keine offensichtliche Erwartung und Python wird einen Fehler auslösen:

TypeError: Error when calling the metaclass bases
    Cannot create a consistent method resolution order (MRO) for bases Second, First

Bearbeiten: Ich sehe mehrere Leute argumentieren, dass die obigen Beispiele keine super()Aufrufe enthalten. Lassen Sie mich daher erklären: In den Beispielen soll gezeigt werden, wie die MRO aufgebaut ist. Sie sollen nicht "first \ nsecond \ Third" oder was auch immer drucken. Sie können - und sollten natürlich mit dem Beispiel herumspielen, super()Aufrufe hinzufügen , sehen, was passiert, und ein tieferes Verständnis des Vererbungsmodells von Python erlangen. Aber mein Ziel hier ist es, es einfach zu halten und zu zeigen, wie der MRO aufgebaut ist. Und es ist so gebaut, wie ich es erklärt habe:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
 <class '__main__.Second'>, <class '__main__.Third'>,
 <class '__main__.First'>,
 <type 'object'>)
rbp
quelle
12
Es wird interessanter (und wohl verwirrender), wenn Sie in First, Second und Third [ pastebin.com/ezTyZ5Wa ] anfangen, super () aufzurufen .
Gatoatigrado
52
Ich denke, das Fehlen von Superanrufen in den ersten Klassen ist ein wirklich großes Problem bei dieser Antwort. ohne zu diskutieren, wie / warum das wichtige kritische Verständnis für die Frage verloren geht.
Sam Hartman
3
Diese Antwort ist einfach falsch. Ohne super () Aufrufe bei den Eltern wird nichts passieren. Die Antwort von @ lifeless ist die richtige.
Cerin
8
@Cerin In diesem Beispiel soll gezeigt werden, wie die MRO aufgebaut ist. Das Beispiel soll NICHT "first \ nsecond \ Third" oder was auch immer drucken. Und die MRO ist in der Tat richtig: Vierte .__ mro__ == (<Klasse ' Haupt. Vierter'>, <Klasse ' Haupt. Zweiter'>, <Klasse ' Haupt. Dritter'>, <Klasse ' Haupt. Erste'>, < Typ 'Objekt'>)
rbp
2
Soweit ich sehen kann, fehlt in dieser Antwort eine der Fragen von OP: "Und was ist, wenn Sie die andere ausführen möchten?". Ich würde gerne die Antwort auf diese Frage sehen. Sollen wir die Basisklasse nur explizit benennen?
Ray
251

Ihr Code und die anderen Antworten sind alle fehlerhaft. Ihnen fehlen die super()Aufrufe in den ersten beiden Klassen, die erforderlich sind, damit die kooperative Unterklasse funktioniert.

Hier ist eine feste Version des Codes:

class First(object):
    def __init__(self):
        super(First, self).__init__()
        print("first")

class Second(object):
    def __init__(self):
        super(Second, self).__init__()
        print("second")

class Third(First, Second):
    def __init__(self):
        super(Third, self).__init__()
        print("third")

Der super()Aufruf findet bei jedem Schritt die nächste Methode im MRO, weshalb First und Second sie ebenfalls haben müssen, andernfalls wird die Ausführung am Ende von beendet Second.__init__().

Das bekomme ich:

>>> Third()
second
first
third
leblos
quelle
90
Was tun, wenn diese Klassen unterschiedliche Parameter benötigen, um sich selbst zu initialisieren?
Calfzhou
2
"Genossenschaftliche Unterklasse"
Quant Metropolis
6
Auf diese Weise werden die Init- Methoden BEIDER Basisklassen ausgeführt, während im ursprünglichen Beispiel nur der erste Init aufgerufen wird, der in der MRO gefunden wurde. Ich denke, das wird durch den Begriff "kooperative Unterklasse" impliziert, aber eine Klarstellung wäre nützlich gewesen ("Explizit ist besser als implizit", wissen Sie;))
Quant Metropolis
1
Ja, wenn Sie verschiedene Parameter an eine Methode übergeben, die über super aufgerufen wird, müssen alle Implementierungen dieser Methode, die die MRO in Richtung object () hinaufgehen, kompatible Signaturen haben. Dies kann durch Schlüsselwortparameter erreicht werden: Akzeptieren Sie mehr Parameter als von der Methode verwendet, und ignorieren Sie zusätzliche. Es wird allgemein als hässlich angesehen, dies zu tun, und in den meisten Fällen ist das Hinzufügen neuer Methoden besser, aber init ist (fast?) Als spezieller Methodenname eindeutig, jedoch mit benutzerdefinierten Parametern.
leblos
15
Das Design der Mehrfachvererbung ist in Python wirklich sehr, sehr schlecht. Die Basisklassen müssen fast wissen, wer sie ableiten wird und wie viele andere Basisklassen die abgeleiteten Klassen in welcher Reihenfolge ableiten werden. Andernfalls superwird sie entweder nicht ausgeführt (aufgrund einer Nichtübereinstimmung der Parameter) oder sie werden nicht aufgerufen einige der Basen (weil Sie nicht superin eine der Basen geschrieben haben, die den Link unterbricht)!
Nawaz
186

Ich wollte die Antwort ein wenig leblos ausarbeiten, denn als ich anfing zu lesen, wie man super () in einer Hierarchie mit mehreren Vererbungen in Python verwendet, bekam ich sie nicht sofort.

Was Sie verstehen müssen, ist, dass super(MyClass, self).__init__()die nächste __init__ Methode gemäß dem verwendeten MRO-Algorithmus (Method Resolution Ordering) im Kontext der vollständigen Vererbungshierarchie bereitgestellt wird .

Dieser letzte Teil ist wichtig zu verstehen. Betrachten wir das Beispiel noch einmal:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

Gemäß diesem Artikel über die Methodenauflösungsreihenfolge von Guido van Rossum wird die Auflösungsreihenfolge __init__(vor Python 2.3) unter Verwendung einer "Tiefenüberquerung von links nach rechts" berechnet:

Third --> First --> object --> Second --> object

Nachdem wir alle Duplikate außer dem letzten entfernt haben, erhalten wir:

Third --> First --> Second --> object

Folgen wir also dem, was passiert, wenn wir eine Instanz der ThirdKlasse instanziieren , z x = Third().

  1. Laut MRO Third.__init__ausgeführt.
    • druckt Third(): entering
    • wird dann super(Third, self).__init__()ausgeführt und MRO kehrt zurück, First.__init__was aufgerufen wird.
  2. First.__init__ wird ausgeführt.
    • druckt First(): entering
    • wird dann super(First, self).__init__()ausgeführt und MRO kehrt zurück, Second.__init__was aufgerufen wird.
  3. Second.__init__ wird ausgeführt.
    • druckt Second(): entering
    • wird dann super(Second, self).__init__()ausgeführt und MRO kehrt zurück, object.__init__was aufgerufen wird.
  4. object.__init__ wird ausgeführt (keine Druckanweisungen im Code dort)
  5. Die Ausführung geht zurück Second.__init__und druckt dannSecond(): exiting
  6. Die Ausführung geht zurück First.__init__und druckt dannFirst(): exiting
  7. Die Ausführung geht zurück Third.__init__und druckt dannThird(): exiting

Hier erfahren Sie, warum das Instanziieren von Third () zu Folgendem führt:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

Der MRO-Algorithmus wurde ab Python 2.3 verbessert, um in komplexen Fällen gut zu funktionieren, aber ich denke, dass die Verwendung des "Durchlaufs von links nach rechts" + "Entfernen von Duplikaten, die für den letzten erwartet werden" in den meisten Fällen immer noch funktioniert (bitte Kommentar, wenn dies nicht der Fall ist). Lesen Sie unbedingt den Blog-Beitrag von Guido!

Visionscaper
quelle
6
Ich verstehe immer noch nicht warum: Inside init von First super (First, self) .__ init __ () ruft den init von Second auf, weil das der MRO diktiert!
user389955
@ user389955 Das erstellte Objekt ist vom Typ Third und verfügt über alle Init-Methoden. Wenn Sie also davon ausgehen, dass MRO bei jedem Superaufruf eine Liste aller Init-Funktionen in einer bestimmten Reihenfolge erstellt, gehen Sie einen Schritt vorwärts, bis Sie das Ende erreichen.
Sreekumar R
15
Ich denke, Schritt 3 braucht mehr Erklärung: Wenn Thirdnicht von geerbt Second, dann super(First, self).__init__würde aufrufen object.__init__und nach der Rückkehr würde "zuerst" gedruckt. Aber weil Thirderbt von beiden Firstund Secondanstatt object.__init__nach First.__init__dem MRO anzurufen, diktiert, dass nur der letzte Aufruf von object.__init__erhalten bleibt und die print-Anweisungen in Firstund Seconderst erreicht werden, wenn sie object.__init__zurückkehren. Da Secondder letzte Anruf getätigt wurde object.__init__, kehrt er nach innen zurück, Secondbevor er zurückkehrt First.
MountainDrew
1
Interessanterweise scheint PyCharm all dies zu wissen (seine Hinweise sprechen darüber, welche Parameter zu welchen Aufrufen von Super gehören. Es hat auch eine Vorstellung von der Kovarianz von Eingaben, so dass es List[subclass]als List[superclass]if subclasseine Unterklasse von erkennt superclass( Liststammt aus dem typingModul von PEP 483) iirc).
Reb.Cabin
Netter Beitrag, aber ich vermisse Informationen in Bezug auf die Argumente der Konstruktoren, dh was passiert, wenn Second und First unterschiedliche Argumente erwarten? Der Konstruktor von First muss einige der Argumente verarbeiten und den Rest an Second weitergeben. Ist das richtig? Es klingt für mich nicht richtig, dass First die erforderlichen Argumente für Second kennen muss.
Christian K.
58

Dies ist als Diamond-Problem bekannt . Die Seite enthält einen Eintrag zu Python. Kurz gesagt, Python ruft die Methoden der Oberklasse von links nach rechts auf.

Monoceres
quelle
Dies ist nicht das Diamantproblem. Das Diamantproblem umfasst vier Klassen und die Frage des OP umfasst nur drei.
Ian Goodfellow
147
objectist der vierte
GP89
28

Auf diese Weise habe ich das Problem gelöst, dass mehrere Vererbungen mit unterschiedlichen Variablen für die Initialisierung und mehrere MixIns mit demselben Funktionsaufruf vorhanden sind. Ich musste explizit Variablen zu übergebenen ** kwargs hinzufügen und eine MixIn-Schnittstelle hinzufügen, um ein Endpunkt für Superaufrufe zu sein.

Hier Aist eine erweiterbare Basisklasse und Bund Csind MixIn-Klassen, die beide Funktionen bereitstellen f. Aund Bbeide erwarten Parameter vin ihrem __init__und Cerwartetw . Die Funktion fnimmt einen Parameter an y. Qerbt von allen drei Klassen. MixInFist die Mixin-Schnittstelle für Bund C.


class A(object):
    def __init__(self, v, *args, **kwargs):
        print "A:init:v[{0}]".format(v)
        kwargs['v']=v
        super(A, self).__init__(*args, **kwargs)
        self.v = v


class MixInF(object):
    def __init__(self, *args, **kwargs):
        print "IObject:init"
    def f(self, y):
        print "IObject:y[{0}]".format(y)


class B(MixInF):
    def __init__(self, v, *args, **kwargs):
        print "B:init:v[{0}]".format(v)
        kwargs['v']=v
        super(B, self).__init__(*args, **kwargs)
        self.v = v
    def f(self, y):
        print "B:f:v[{0}]:y[{1}]".format(self.v, y)
        super(B, self).f(y)


class C(MixInF):
    def __init__(self, w, *args, **kwargs):
        print "C:init:w[{0}]".format(w)
        kwargs['w']=w
        super(C, self).__init__(*args, **kwargs)
        self.w = w
    def f(self, y):
        print "C:f:w[{0}]:y[{1}]".format(self.w, y)
        super(C, self).f(y)


class Q(C,B,A):
    def __init__(self, v, w):
        super(Q, self).__init__(v=v, w=w)
    def f(self, y):
        print "Q:f:y[{0}]".format(y)
        super(Q, self).f(y)
brent.payne
quelle
Ich denke, dies sollte vielleicht eine separate Frage und Antwort sein, da die MRO für sich genommen ein ausreichend großes Thema ist, ohne sich mit unterschiedlichen Argumenten über Funktionen mit Vererbung hinweg zu befassen (Mehrfachvererbung ist ein Sonderfall davon).
leblos
8
Theoretisch ja. Praktisch ist dieses Szenario jedes Mal aufgetreten, wenn ich auf Diamond-Vererbung in Python gestoßen bin. Deshalb habe ich es hier hinzugefügt. Seitdem gehe ich jedes Mal dorthin, wenn ich die Vererbung von Diamanten nicht sauber vermeiden kann. Hier sind einige zusätzliche Links für zukünftige mich: rhettinger.wordpress.com/2011/05/26/super-considered-super code.activestate.com/recipes/…
brent.payne
Was wir wollen, sind Programme mit semantisch bedeutsamen Parameternamen. In diesem Beispiel werden jedoch fast alle Parameter anonym benannt, was es für den ursprünglichen Programmierer viel schwieriger macht, den Code zu dokumentieren, und für einen anderen Programmierer, den Code zu lesen.
Arthur
Eine Pull-Anfrage an das Github-Repo mit beschreibenden Namen wäre
willkommen
@ brent.payne Ich denke, @Arthur bedeutete, dass Ihr gesamter Ansatz auf der Verwendung von args/ kwargsund nicht auf benannten Parametern beruht .
Max
25

Ich verstehe, dass dies die super()Frage nicht direkt beantwortet , aber ich denke, dass es relevant genug ist, um es zu teilen.

Es gibt auch eine Möglichkeit, jede geerbte Klasse direkt aufzurufen:


class First(object):
    def __init__(self):
        print '1'

class Second(object):
    def __init__(self):
        print '2'

class Third(First, Second):
    def __init__(self):
        Second.__init__(self)

Es genügt zu bemerken , dass , wenn Sie es auf diese Weise tun, werden Sie jede manuell aufrufen müssen , wie ich bin ziemlich sicher , First‚s __init__()wird nicht aufgerufen werden.

Seaux
quelle
5
Es wird nicht aufgerufen, weil Sie nicht jede geerbte Klasse aufgerufen haben. Das Problem ist vielmehr , dass , wenn Firstund Secondbeide eine andere Klasse zu erben und wird direkt dann diese gemeinsame Klasse (Ausgangspunkt des Diamanten) zweimal aufgerufen aufrufen. Super vermeidet dies.
Trilarion
@ Trilarion Ja, ich war zuversichtlich, dass es nicht würde. Ich wusste es jedoch nicht definitiv und wollte nicht sagen, als ob ich es getan hätte, obwohl es sehr unwahrscheinlich war. Das ist ein guter Punkt, objectwenn man zweimal angerufen wird. Daran habe ich nicht gedacht. Ich wollte nur darauf hinweisen, dass Sie Elternklassen direkt aufrufen.
Seaux
Leider bricht dies, wenn init versucht, auf private Methoden zuzugreifen :(
Erik Aronesty
21

Insgesamt

Angenommen, alles stammt von object(Sie sind allein, wenn dies nicht der Fall ist), berechnet Python eine Methodenauflösungsreihenfolge (MRO) basierend auf Ihrem Klassenvererbungsbaum. Der MRO erfüllt 3 Eigenschaften:

  • Kinder einer Klasse kommen vor ihre Eltern
  • Linke Eltern kommen vor rechten Eltern
  • Eine Klasse erscheint nur einmal im MRO

Wenn keine solche Reihenfolge vorhanden ist, treten Python-Fehler auf. Das Innenleben ist eine C3-Linerisierung der Klassenvorfahren. Lesen Sie hier alles darüber: https://www.python.org/download/releases/2.3/mro/

In beiden folgenden Beispielen heißt es also:

  1. Kind
  2. Links
  3. Recht
  4. Elternteil

Wenn eine Methode aufgerufen wird, wird diese Methode zum ersten Mal in der MRO aufgerufen. Jede Klasse, die diese Methode nicht implementiert, wird übersprungen. Jeder Aufruf superinnerhalb dieser Methode ruft das nächste Auftreten dieser Methode in der MRO auf. Folglich ist es wichtig, in welcher Reihenfolge Sie Klassen in die Vererbung einfügen und wohin Sie die Aufrufe setzensuper in den Methoden platzieren.

Mit superzuerst in jeder Methode

class Parent(object):
    def __init__(self):
        super(Parent, self).__init__()
        print "parent"

class Left(Parent):
    def __init__(self):
        super(Left, self).__init__()
        print "left"

class Right(Parent):
    def __init__(self):
        super(Right, self).__init__()
        print "right"

class Child(Left, Right):
    def __init__(self):
        super(Child, self).__init__()
        print "child"

Child() Ausgänge:

parent
right
left
child

Mit superlast in jeder Methode

class Parent(object):
    def __init__(self):
        print "parent"
        super(Parent, self).__init__()

class Left(Parent):
    def __init__(self):
        print "left"
        super(Left, self).__init__()

class Right(Parent):
    def __init__(self):
        print "right"
        super(Right, self).__init__()

class Child(Left, Right):
    def __init__(self):
        print "child"
        super(Child, self).__init__()

Child() Ausgänge:

child
left
right
parent
Zags
quelle
Ich sehe, dass Sie Leftmit super()von zugreifen können Child. Angenommen, ich möchte Rightvon innen darauf zugreifen Child. Gibt es eine Möglichkeit , den Zugang Rightvon ChildSuper mit? Oder soll ich direkt Rightvon innen anrufen super?
Alpha_989
4
@ alpha_989 Wenn Sie nur auf die Methode einer bestimmten Klasse zugreifen möchten, sollten Sie direkt auf diese Klasse verweisen, anstatt super zu verwenden. Bei Super geht es darum, der Vererbungskette zu folgen und nicht zur Methode einer bestimmten Klasse zu gelangen.
Zags
1
Vielen Dank, dass Sie ausdrücklich erwähnt haben, dass eine Klasse nur einmal im MRO angezeigt wird. Dies löste mein Problem. Jetzt verstehe ich endlich, wie Mehrfachvererbung funktioniert. Jemand musste die Eigenschaften von MRO erwähnen!
Tushar Vazirani
18

Über den Kommentar von @ calfzhou können Sie wie gewohnt Folgendes verwenden:**kwargs :

Online laufendes Beispiel

class A(object):
  def __init__(self, a, *args, **kwargs):
    print("A", a)

class B(A):
  def __init__(self, b, *args, **kwargs):
    super(B, self).__init__(*args, **kwargs)
    print("B", b)

class A1(A):
  def __init__(self, a1, *args, **kwargs):
    super(A1, self).__init__(*args, **kwargs)
    print("A1", a1)

class B1(A1, B):
  def __init__(self, b1, *args, **kwargs):
    super(B1, self).__init__(*args, **kwargs)
    print("B1", b1)


B1(a1=6, b1=5, b="hello", a=None)

Ergebnis:

A None
B hello
A1 6
B1 5

Sie können sie auch positionell verwenden:

B1(5, 6, b="hello", a=None)

Aber Sie müssen sich an die MRO erinnern, es ist wirklich verwirrend.

Ich kann ein wenig nervig sein, aber ich habe bemerkt, dass die Leute jedes Mal vergessen haben, eine Methode zu verwenden *argsund **kwargswenn sie sie überschreiben, während dies eine der wenigen wirklich nützlichen und vernünftigen Anwendungen dieser 'magischen Variablen' ist.

Marco Sulla
quelle
Wow das ist echt hässlich. Es ist eine Schande, dass Sie nicht einfach sagen können, welche bestimmte Superklasse Sie anrufen möchten. Dies gibt mir jedoch noch mehr Anreiz, Komposition zu verwenden und Mehrfachvererbung wie die Pest zu vermeiden.
Tom Busby
15

Ein weiterer noch nicht behandelter Punkt ist die Übergabe von Parametern für die Initialisierung von Klassen. Da das Ziel vonsuper von der Unterklasse abhängt, besteht die einzige gute Möglichkeit, Parameter zu übergeben, darin, sie alle zusammen zu packen. Achten Sie dann darauf, dass Sie nicht denselben Parameternamen mit unterschiedlichen Bedeutungen haben.

Beispiel:

class A(object):
    def __init__(self, **kwargs):
        print('A.__init__')
        super().__init__()

class B(A):
    def __init__(self, **kwargs):
        print('B.__init__ {}'.format(kwargs['x']))
        super().__init__(**kwargs)


class C(A):
    def __init__(self, **kwargs):
        print('C.__init__ with {}, {}'.format(kwargs['a'], kwargs['b']))
        super().__init__(**kwargs)


class D(B, C): # MRO=D, B, C, A
    def __init__(self):
        print('D.__init__')
        super().__init__(a=1, b=2, x=3)

print(D.mro())
D()

gibt:

[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
D.__init__
B.__init__ 3
C.__init__ with 1, 2
A.__init__

Das __init__direkte Aufrufen der Superklasse zur direkteren Zuweisung von Parametern ist verlockend, schlägt jedoch fehl, wenn es welche gibtsuper in einer Superklasse Aufruf erfolgt und / oder die MRO geändert wird und Klasse A je nach Implementierung mehrmals aufgerufen werden kann.

Fazit: Kooperative Vererbung und Super- und spezifische Parameter für die Initialisierung arbeiten nicht sehr gut zusammen.

Trilarion
quelle
5
class First(object):
  def __init__(self, a):
    print "first", a
    super(First, self).__init__(20)

class Second(object):
  def __init__(self, a):
    print "second", a
    super(Second, self).__init__()

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__(10)
    print "that's it"

t = Third()

Ausgabe ist

first 10
second 20
that's it

Call to Third () sucht den in Third definierten Init . Und der Aufruf von super in dieser Routine ruft init auf, das in First definiert ist. MRO = [Erste, Zweite]. Jetzt ruft der Aufruf von super in init, definiert in First, die Suche nach MRO auf und findet init, definiert in Second, und jeder Aufruf von super trifft das Standardobjekt init . Ich hoffe, dieses Beispiel verdeutlicht das Konzept.

Wenn Sie nicht super von First anrufen. Die Kette stoppt und Sie erhalten die folgende Ausgabe.

first 10
that's it
Seraj Ahmad
quelle
1
Das liegt daran, dass Sie in der Klasse First zuerst 'print' und dann 'super' genannt haben.
Rocky Qi
2
das sollte die aufrufende Reihenfolge veranschaulichen
Seraj Ahmad
4

Beim Lernen von Python auf dem Weg lerne ich etwas, das als super () bezeichnet wird, eine eingebaute Funktion, wenn nicht falsch. Das Aufrufen der Funktion super () kann dazu beitragen, dass die Vererbung die Eltern und Geschwister passiert und Sie klarer sehen. Ich bin noch ein Anfänger, aber ich liebe es, meine Erfahrungen mit der Verwendung dieses super () in Python2.7 zu teilen.

Wenn Sie die Kommentare auf dieser Seite gelesen haben, hören Sie von Method Resolution Order (MRO), wobei die Methode die von Ihnen geschriebene Funktion ist. MRO verwendet zum Suchen und Ausführen das Schema Tiefe zuerst von links nach rechts. Sie können mehr darüber recherchieren.

Durch Hinzufügen der Funktion super ()

super(First, self).__init__() #example for class First.

Sie können mehrere Instanzen und 'Familien' mit super () verbinden, indem Sie jede einzelne Instanz hinzufügen. Und es wird die Methoden ausführen, sie durchgehen und sicherstellen, dass Sie nichts verpasst haben! Wenn Sie sie jedoch vorher oder nachher hinzufügen, werden Sie feststellen, ob Sie die Lernpythonthehardway-Übung 44 durchgeführt haben. Lassen Sie den Spaß beginnen!

Im folgenden Beispiel können Sie es kopieren, einfügen und ausführen:

class First(object):
    def __init__(self):

        print("first")

class Second(First):
    def __init__(self):
        print("second (before)")
        super(Second, self).__init__()
        print("second (after)")

class Third(First):
    def __init__(self):
        print("third (before)")
        super(Third, self).__init__()
        print("third (after)")


class Fourth(First):
    def __init__(self):
        print("fourth (before)")
        super(Fourth, self).__init__()
        print("fourth (after)")


class Fifth(Second, Third, Fourth):
    def __init__(self):
        print("fifth (before)")
        super(Fifth, self).__init__()
        print("fifth (after)")

Fifth()

Wie läuft es? Die Instanz von 5th () wird so aussehen. Jeder Schritt geht von Klasse zu Klasse, wo die Superfunktion hinzugefügt wird.

1.) print("fifth (before)")
2.) super()>[Second, Third, Fourth] (Left to right)
3.) print("second (before)")
4.) super()> First (First is the Parent which inherit from object)

Der Elternteil wurde gefunden und es wird weiter bis zum dritten und vierten gehen !!

5.) print("third (before)")
6.) super()> First (Parent class)
7.) print ("Fourth (before)")
8.) super()> First (Parent class)

Jetzt wurde auf alle Klassen mit super () zugegriffen! Die übergeordnete Klasse wurde gefunden und ausgeführt und entpackt nun weiterhin die Funktion in den Vererbungen, um die Codes fertigzustellen.

9.) print("first") (Parent)
10.) print ("Fourth (after)") (Class Fourth un-box)
11.) print("third (after)") (Class Third un-box)
12.) print("second (after)") (Class Second un-box)
13.) print("fifth (after)") (Class Fifth un-box)
14.) Fifth() executed

Das Ergebnis des obigen Programms:

fifth (before)
second (before
third (before)
fourth (before)
first
fourth (after)
third (after)
second (after)
fifth (after)

Durch Hinzufügen von super () kann ich klarer sehen, wie Python meine Codierung ausführen würde, und sicherstellen, dass die Vererbung auf die von mir beabsichtigte Methode zugreifen kann.

Werde dich treffen
quelle
Danke für die ausführliche Demo!
Tushar Vazirani
3

Ich möchte hinzufügen, was @Visionscaper oben sagt :

Third --> First --> object --> Second --> object

In diesem Fall filtert der Interpreter die Objektklasse nicht heraus, weil sie dupliziert wurde, sondern weil Second an einer Kopfposition und nicht an der Endposition in einer Hierarchie-Teilmenge erscheint. Das Objekt erscheint nur in Endpositionen und wird im C3-Algorithmus nicht als starke Position zur Bestimmung der Priorität angesehen.

Die Linearisierung (mro) einer Klasse C, L (C) ist die

  • die Klasse C.
  • plus die Fusion von
    • Linearisierung seiner Eltern P1, P2, .. = L (P1, P2, ...) und
    • die Liste seiner Eltern P1, P2, ..

Die linearisierte Zusammenführung erfolgt durch Auswahl der allgemeinen Klassen, die als Listenkopf und nicht als Endpunkt angezeigt werden, da die Reihenfolge wichtig ist (wird unten deutlich).

Die Linearisierung von Third kann wie folgt berechnet werden:

    L(O)  := [O]  // the linearization(mro) of O(object), because O has no parents

    L(First)  :=  [First] + merge(L(O), [O])
               =  [First] + merge([O], [O])
               =  [First, O]

    // Similarly, 
    L(Second)  := [Second, O]

    L(Third)   := [Third] + merge(L(First), L(Second), [First, Second])
                = [Third] + merge([First, O], [Second, O], [First, Second])
// class First is a good candidate for the first merge step, because it only appears as the head of the first and last lists
// class O is not a good candidate for the next merge step, because it also appears in the tails of list 1 and 2, 
                = [Third, First] + merge([O], [Second, O], [Second])
// class Second is a good candidate for the second merge step, because it appears as the head of the list 2 and 3
                = [Third, First, Second] + merge([O], [O])            
                = [Third, First, Second, O]

Also für eine super () Implementierung im folgenden Code:

class First(object):
  def __init__(self):
    super(First, self).__init__()
    print "first"

class Second(object):
  def __init__(self):
    super(Second, self).__init__()
    print "second"

class Third(First, Second):
  def __init__(self):
    super(Third, self).__init__()
    print "that's it"

Es wird deutlich, wie diese Methode gelöst wird

Third.__init__() ---> First.__init__() ---> Second.__init__() ---> 
Object.__init__() ---> returns ---> Second.__init__() -
prints "second" - returns ---> First.__init__() -
prints "first" - returns ---> Third.__init__() - prints "that's it"
supi
quelle
"Vielmehr liegt es daran, dass Second in einer Kopfposition und nicht in der Endposition in einer Hierarchie-Teilmenge erscheint." Es ist nicht klar, was eine Kopf- oder Schwanzposition ist, was eine Hierarchie-Teilmenge ist oder auf welche Teilmenge Sie sich beziehen.
OrangeSherbet
Die Schwanzposition bezieht sich auf Klassen, die in der Klassenhierarchie höher sind und umgekehrt. Die Basisklasse 'Objekt' befindet sich am Ende des Endes. Der Schlüssel zum Verständnis des mro-Algorithmus ist, wie 'Second' als Super von 'First' erscheint. Wir würden normalerweise annehmen, dass es sich um die 'Objekt'-Klasse handelt. Das ist wahr, aber nur in der Perspektive der "Ersten" Klasse. Aus der Sicht der 'dritten' Klasse ist die Hierarchiereihenfolge für 'Erste' jedoch unterschiedlich und wird wie oben gezeigt berechnet. Der mro-Algorithmus versucht, diese Perspektive (oder Hierarchie-Teilmenge) für alle mehrfach geerbten Klassen zu erstellen
supi
3

In Python 3.5+ sieht die Vererbung vorhersehbar und für mich sehr gut aus. Bitte schauen Sie sich diesen Code an:

class Base(object):
  def foo(self):
    print("    Base(): entering")
    print("    Base(): exiting")


class First(Base):
  def foo(self):
    print("   First(): entering Will call Second now")
    super().foo()
    print("   First(): exiting")


class Second(Base):
  def foo(self):
    print("  Second(): entering")
    super().foo()
    print("  Second(): exiting")


class Third(First, Second):
  def foo(self):
    print(" Third(): entering")
    super().foo()
    print(" Third(): exiting")


class Fourth(Third):
  def foo(self):
    print("Fourth(): entering")
    super().foo()
    print("Fourth(): exiting")

Fourth().foo()
print(Fourth.__mro__)

Ausgänge:

Fourth(): entering
 Third(): entering
   First(): entering Will call Second now
  Second(): entering
    Base(): entering
    Base(): exiting
  Second(): exiting
   First(): exiting
 Third(): exiting
Fourth(): exiting
(<class '__main__.Fourth'>, <class '__main__.Third'>, <class '__main__.First'>, <class '__main__.Second'>, <class '__main__.Base'>, <class 'object'>)

Wie Sie sehen können, wird foo genau EIN Mal für jede geerbte Kette in derselben Reihenfolge aufgerufen, in der sie geerbt wurde. Sie können diese Bestellung erhalten, indem Sie anrufen . mro :

Viertens -> Dritte -> Erste -> Zweite -> Basis -> Objekt

rfedorov
quelle
2

Vielleicht kann noch etwas hinzugefügt werden, ein kleines Beispiel mit Django rest_framework und Dekorateuren. Dies gibt eine Antwort auf die implizite Frage: "Warum sollte ich das überhaupt wollen?"

Wie gesagt: Wir arbeiten mit Django rest_framework und verwenden generische Ansichten. Für jeden Objekttyp in unserer Datenbank gibt es eine Ansichtsklasse, die GET und POST für Objektlisten bereitstellt, und eine andere Ansichtsklasse, die GET bereitstellt , PUT und DELETE für einzelne Objekte.

Jetzt möchten wir POST, PUT und DELETE mit Djangos login_required dekorieren. Beachten Sie, wie dies beide Klassen berührt, jedoch nicht alle Methoden in beiden Klassen.

Eine Lösung kann mehrfach vererbt werden.

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required

class LoginToPost:
    @method_decorator(login_required)
    def post(self, arg, *args, **kwargs):
        super().post(arg, *args, **kwargs)

Ebenso für die anderen Methoden.

In der Vererbungsliste meiner konkreten Klassen würde ich meine LoginToPostVorher ListCreateAPIViewund LoginToPutOrDeleteVorher hinzufügen RetrieveUpdateDestroyAPIView. Meine konkreten Klassen getwürden nicht dekoriert bleiben.

Mariotomo
quelle
1

Poste diese Antwort für meine zukünftige Referenz.

Python Multiple Inheritance sollte ein Diamantmodell verwenden und die Funktionssignatur sollte sich im Modell nicht ändern.

    A
   / \
  B   C
   \ /
    D

Das Beispielcode-Snippet wäre: -

class A:
    def __init__(self, name=None):
        #  this is the head of the diamond, no need to call super() here
        self.name = name

class B(A):
    def __init__(self, param1='hello', **kwargs):
        super().__init__(**kwargs)
        self.param1 = param1

class C(A):
    def __init__(self, param2='bye', **kwargs):
        super().__init__(**kwargs)
        self.param2 = param2

class D(B, C):
    def __init__(self, works='fine', **kwargs):
        super().__init__(**kwargs)
        print(f"{works=}, {self.param1=}, {self.param2=}, {self.name=}")

d = D(name='Testing')

Hier ist Klasse A. object

Akhil Nadh PC
quelle
1
Asollte auch anrufen __init__. Ahat die Methode nicht "erfunden" __init__, daher kann nicht davon ausgegangen werden, dass eine andere Klasse Afrüher in ihrer MRO war. Die einzige Klasse, deren __init__Methode nicht aufruft (und nicht aufrufen sollte), super().__init__ist object.
Chepper
Ja. Deshalb habe ich A geschrieben. objectVielleicht denke ich, ich sollte class A (object) : stattdessen schreiben
Akhil Nadh PC
Akann nicht sein, objectwenn Sie einen Parameter zu seiner hinzufügen __init__.
Chepper