744 Stimmen

Was bewirkt 'super' in Python? - Unterschied zwischen super().__init__() und explizitem superclass __init__()

Was ist der Unterschied zwischen:

class Child(SomeBaseClass):
    def __init__(self):
        super(Child, self).__init__()

und:

class Child(SomeBaseClass):
    def __init__(self):
        SomeBaseClass.__init__(self)

Ich habe gesehen super wird häufig in Klassen mit nur einfacher Vererbung verwendet. Ich kann verstehen, warum Sie es in der Mehrfachvererbung verwenden würden, aber mir ist nicht klar, was die Vorteile der Verwendung in dieser Art von Situation sind.

25voto

Raymond Hettinger Punkte 197261

Super() in aller Kürze

  • Jede Python-Instanz hat eine Klasse, die sie erstellt hat.
  • Jede Klasse in Python hat eine Kette von Vorgängerklassen.
  • Eine Methode, die super() verwendet, delegiert die Arbeit an den nächsten Vorfahren in der Kette für die Klasse der Instanz.

Beispiel

Dieses kleine Beispiel deckt alle interessanten Fälle ab:

class A:
    def m(self):
        print('A')

class B(A):
    def m(self):
        print('B start')
        super().m()
        print('B end')

class C(A):
    def m(self):
        print('C start')
        super().m()
        print('C end')

class D(B, C):
    def m(self):
        print('D start')
        super().m()
        print('D end')

Die genaue Reihenfolge der Aufrufe wird durch die Instanz bestimmt, von der aus die Methode aufgerufen wird:

>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()

Zum Beispiel a gibt es keinen Superanruf:

>>> a.m()
A

Zum Beispiel b ist die Vorläuferkette B -> A -> object :

>>> type(b).__mro__   
(<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

>>> b.m()
B start
A
B end

Zum Beispiel c ist die Vorläuferkette C -> A -> object :

>>> type(c).__mro__   
(<class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

>>> c.m()
C start
A
C end

Zum Beispiel d ist die Ahnenkette interessanter D -> B -> C -> A -> object ( mro steht für die Reihenfolge der Methodenauflösung) :

>>> type(d).__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

>>> d.m()
D start
B start
C start
A
C end
B end
D end

Weitere Informationen

Nach der Beantwortung der Frage "Was kann super in Python?" stellt sich nun die Frage, wie man es effektiv einsetzt. Siehe dies Schritt-für-Schritt-Anleitung oder dies 45-minütiges Video .

12voto

run_the_race Punkte 2073

Viele tolle Antworten, aber für visuelle Lernende: Lassen Sie uns zunächst mit Argumenten zu Super und dann ohne erkunden. super inheritance tree example

Stellen Sie sich vor, es gibt eine Instanz jack die aus der Klasse Jack der die in der Abbildung grün dargestellte Vererbungskette hat. Aufrufen:

super(Jack, jack).method(...)

wird die MRO (Method Resolution Order) von jack (sein Vererbungsbaum in einer bestimmten Reihenfolge), und beginnt die Suche ab Jack . Warum kann man eine Elternklasse anbieten? Nun, wenn wir die Suche von der Instanz jack würde es die Instanzmethode finden, denn es geht ja darum, die Elternmethode zu finden.

Wenn man super keine Argumente liefert, ist das erste Argument, das übergeben wird, die Klasse von self , und das zweite Argument, das übergeben wird, ist self . Diese werden in Python3 automatisch für Sie errechnet.

Aber sagen wir, wir wollen nicht Jack Methode, statt der Übergabe in Jack könnten wir in Jen um die Suche nach der Methode von oben nach unten zu beginnen Jen .

Es wird jeweils eine Ebene gesucht (Breite, nicht Tiefe), z. B. wenn Adam y Sue haben beide die erforderliche Methode, die von Sue wird zuerst gefunden.

Wenn Cain y Sue beide hatten die erforderliche Methode, Cain Methode zuerst aufgerufen werden würde. Dies entspricht im Code dem:

Class Jen(Cain, Sue):

MRO ist von links nach rechts.

4voto

cdude Punkte 121

Im Falle der Mehrfachvererbung wollen Sie normalerweise die Initialisierungen beider Elternteile aufrufen, nicht nur die des ersten. Anstatt immer die Basisklasse zu verwenden, findet super() die Klasse, die in der Methodenauflösungsreihenfolge (MRO) die nächste ist, und gibt das aktuelle Objekt als eine Instanz dieser Klasse zurück. Zum Beispiel:

class Base(object):
    def __init__(self):
        print("initializing Base")

class ChildA(Base):
    def __init__(self):
        print("initializing ChildA")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("initializing ChildB")
        super().__init__()

class Grandchild(ChildA, ChildB):
    def __init__(self):
        print("initializing Grandchild")
        super().__init__()

Grandchild()

führt zu

initializing Grandchild
initializing ChildA
initializing Base

Ersetzen von Base.__init__(self) mit super().__init__() führt zu

initializing Grandchild
initializing ChildA
initializing ChildB
initializing Base

wie gewünscht.

3voto

Aviad Rozenhek Punkte 2110

Einige großartige Antworten hier, aber sie behandeln nicht die Frage, wie man super() für den Fall, dass verschiedene Klassen in der Hierarchie unterschiedliche Signaturen haben ... insbesondere im Fall von __init__

diesen Teil zu beantworten und in der Lage zu sein, die super() Ich würde vorschlagen, meine Antwort zu lesen. super() und Änderung der Signatur von kooperativen Methoden .

hier ist die Lösung für dieses Szenario:

  1. müssen die Klassen der obersten Ebene in Ihrer Hierarchie von einer benutzerdefinierten Klasse wie SuperObject :
  2. Wenn Klassen unterschiedliche Argumente annehmen können, übergeben Sie immer alle Argumente, die Sie erhalten haben, als Schlüsselwortargumente an die Superfunktion, und akzeptieren Sie immer **kwargs .
class SuperObject:        
    def __init__(self, **kwargs):
        print('SuperObject')
        mro = type(self).__mro__
        assert mro[-1] is object
        if mro[-2] is not SuperObject:
            raise TypeError(
                'all top-level classes in this hierarchy must inherit from SuperObject',
                'the last class in the MRO should be SuperObject',
                f'mro={[cls.__name__ for cls in mro]}'
            )

        # super().__init__ is guaranteed to be object.__init__        
        init = super().__init__
        init()

Anwendungsbeispiel:

class A(SuperObject):
    def __init__(self, **kwargs):
        print("A")
        super(A, self).__init__(**kwargs)

class B(SuperObject):
    def __init__(self, **kwargs):
        print("B")
        super(B, self).__init__(**kwargs)

class C(A):
    def __init__(self, age, **kwargs):
        print("C",f"age={age}")
        super(C, self).__init__(age=age, **kwargs)

class D(B):
    def __init__(self, name, **kwargs):
        print("D", f"name={name}")
        super(D, self).__init__(name=name, **kwargs)

class E(C,D):
    def __init__(self, name, age, *args, **kwargs):
        print( "E", f"name={name}", f"age={age}")
        super(E, self).__init__(name=name, age=age, *args, **kwargs)

E(name='python', age=28)

Ausgabe:

E name=python age=28
C age=28
A
D name=python
B
SuperObject

2voto

tsh Punkte 3669

Betrachten Sie den folgenden Code:

class X():
    def __init__(self):
        print("X")

class Y(X):
    def __init__(self):
        # X.__init__(self)
        super(Y, self).__init__()
        print("Y")

class P(X):
    def __init__(self):
        super(P, self).__init__()
        print("P")

class Q(Y, P):
    def __init__(self):
        super(Q, self).__init__()
        print("Q")

Q()

Wenn Sie den Konstruktor von Y a X.__init__ erhalten Sie:

X
Y
Q

Aber mit super(Y, self).__init__() erhalten Sie:

X
P
Y
Q

Und P o Q kann sogar von einer anderen Datei stammen, die Sie beim Schreiben nicht kennen X y Y . Sie werden also im Grunde nicht wissen, was super(Child, self) auf die Sie sich beziehen werden, wenn Sie schreiben class Y(X) ist sogar die Signatur von Y so einfach wie Y(X) . Deshalb könnte Super die bessere Wahl sein.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X