web-dev-qa-db-de.com

Was macht "super" in Python?

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, dass super ziemlich oft in Klassen mit nur einer Vererbung verwendet wird. Ich kann verstehen, warum Sie es bei der Mehrfachvererbung verwenden würden, aber ich bin nicht klar, welche Vorteile es bringt, wenn Sie es in einer solchen Situation nutzen.

462
user25785

Die Vorteile von super() in der Einzelvererbung sind minimal - meistens müssen Sie den Namen der Basisklasse nicht in jede Methode hart codieren, die ihre übergeordneten Methoden verwendet.

Es ist jedoch fast unmöglich, die Mehrfachvererbung ohne super() zu verwenden. Dazu gehören gängige Redewendungen wie Mixins, Interfaces, abstrakte Klassen usw. Dies gilt auch für Code, der später Ihren erweitert. Wenn jemand später eine Klasse schreiben wollte, die Child und ein Mixin erweiterte, würde ihr Code nicht richtig funktionieren.

267
John Millikin

Was ist der Unterschied?

SomeBaseClass.__init__(self) 

bedeutet, SomeBaseClasss __init__ aufzurufen. während

super(Child, self).__init__()

bedeutet, einen gebundenen __init__ von der übergeordneten Klasse aufzurufen, der Child in der Method Resolution Order (MRO) der Instanz folgt. 

Wenn es sich bei der Instanz um eine Unterklasse von Child handelt, wird im MRO möglicherweise ein anderes übergeordnetes Element angezeigt. 

Einfach erklärt

Wenn Sie eine Klasse schreiben, möchten Sie, dass andere Klassen sie verwenden können. super() erleichtert anderen Klassen die Verwendung der Klasse, die Sie schreiben.

Wie Bob Martin sagt, können Sie mit einer guten Architektur die Entscheidungsfindung so lange wie möglich verschieben.

super() kann diese Art von Architektur ermöglichen.

Wenn eine andere Klasse die von Ihnen geschriebene Klasse unterordnet, kann sie auch von anderen Klassen erben. Diese Klassen könnten einen __init__ haben, der auf diesen __init__ folgt, basierend auf der Reihenfolge der Klassen zur Methodenauflösung.

Ohne super würden Sie wahrscheinlich das übergeordnete Element der Klasse, die Sie schreiben, hart codieren (wie im Beispiel). Dies würde bedeuten, dass Sie den nächsten __init__ im MRO nicht aufrufen würden und Sie den Code darin nicht wiederverwenden könnten.

Wenn Sie Ihren eigenen Code für den persönlichen Gebrauch schreiben, ist Ihnen diese Unterscheidung möglicherweise egal. Wenn Sie jedoch möchten, dass andere Ihren Code verwenden, ist die Verwendung von super eine Sache, die den Benutzern des Codes mehr Flexibilität bietet.

Python 2 gegen 3

Dies funktioniert in Python 2 und 3:

super(Child, self).__init__()

Dies funktioniert nur in Python 3:

super().__init__()

Es funktioniert ohne Argumente, indem im Stack-Frame nach oben gerückt wird und das erste Argument an die Methode übergeben wird (normalerweise self für eine Instanzmethode oder cls für eine Klassenmethode - es könnten jedoch auch andere Namen sein) und die Klasse (z freie Variablen (es wird mit dem Namen __class__ als freie Abschlussvariable in der Methode nachgeschlagen).

Ich möchte lieber die kreuzkompatible Methode zur Verwendung von Child demonstrieren. Wenn Sie jedoch nur Python 3 verwenden, können Sie es ohne Argumente aufrufen.

Indirektion mit Forward-Kompatibilität

Was gibt es dir? Für die Einzelvererbung sind die Beispiele aus der Frage vom Standpunkt der statischen Analyse aus praktisch identisch. Wenn Sie super verwenden, erhalten Sie jedoch eine Ebene der Weiterleitung mit Vorwärtskompatibilität.

Vorwärtskompatibilität ist für erfahrene Entwickler sehr wichtig. Sie möchten, dass Ihr Code mit minimalen Änderungen weiterarbeitet, wenn Sie ihn ändern. Wenn Sie Ihren Revisionsverlauf betrachten, möchten Sie genau sehen, was sich wann geändert hat. 

Sie können mit einer einfachen Vererbung beginnen, aber wenn Sie sich entscheiden, eine weitere Basisklasse hinzuzufügen, müssen Sie nur die Zeile mit den Basiswerten ändern. Wenn sich die Basis in einer Klasse ändert, von der Sie erben (beispielsweise wird ein Mixin hinzugefügt), ändern Sie die Einstellung nichts in dieser Klasse. Insbesondere in Python 2 kann es schwierig sein, die Argumente für super und die richtigen Methodenargumente richtig zu finden. Wenn Sie wissen, dass Sie super korrekt mit der Einzelvererbung verwenden, ist das Debuggen in Zukunft weniger schwierig.

Abhängigkeitsspritze

Andere Personen können Ihren Code verwenden und Eltern in die Methodenauflösung einspeisen:

class SomeBaseClass(object):
    def __init__(self):
        print('SomeBaseClass.__init__(self) called')

class UnsuperChild(SomeBaseClass):
    def __init__(self):
        print('UnsuperChild.__init__(self) called')
        SomeBaseClass.__init__(self)

class SuperChild(SomeBaseClass):
    def __init__(self):
        print('SuperChild.__init__(self) called')
        super(SuperChild, self).__init__()

Angenommen, Sie fügen Ihrem Objekt eine weitere Klasse hinzu und möchten eine Klasse zwischen Foo und Bar einfügen (zum Testen oder aus einem anderen Grund):

class InjectMe(SomeBaseClass):
    def __init__(self):
        print('InjectMe.__init__(self) called')
        super(InjectMe, self).__init__()

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

Die Verwendung des un-super-Kindes kann die Abhängigkeit nicht beeinflussen, da das von Ihnen verwendete Kind die Methode hart codiert hat, die nach seiner eigenen Methode aufgerufen werden muss:

>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called

Die Klasse mit dem Kind, das super verwendet, kann die Abhängigkeit jedoch korrekt einfügen:

>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called

Einen Kommentar ansprechen

Warum in aller Welt wäre das nützlich? 

Python linearisiert einen komplizierten Vererbungsbaum mit dem C3-Linearisierungsalgorithmus , um eine Method Resolution Order (MRO) zu erstellen.

Wir wollen, dass Methoden in dieser Reihenfolge nachgeschlagen werden.

Damit eine in einem übergeordneten Element definierte Methode das nächste in dieser Reihenfolge ohne super finden kann, muss es dies tun 

  1. holen Sie sich den Mro vom Typ der Instanz 
  2. suchen Sie nach dem Typ, der die Methode definiert
  3. finden Sie den nächsten Typ mit der Methode
  4. binden Sie diese Methode und rufen Sie sie mit den erwarteten Argumenten auf

super sollte keinen Zugriff auf UnsuperChild haben. Warum ist die Schlussfolgerung "Vermeiden Sie immer InjectMe" zu verwenden? Was fehlt mir hier?

super hat not keinen Zugriff auf UnsuperChild. InjectMe hat Zugriff auf UnsuperInjector - und kann die Methode dieser Klasse nicht mit der Methode aufrufen, die sie von InjectMe erbt. 

Beide untergeordneten Klassen beabsichtigen, eine Methode mit demselben Namen aufzurufen, die als nächster in der MRO erscheint. Dies könnte die Klasse eine andere -Klasse sein, von der sie nicht wusste, wann sie erstellt wurde. 

Derjenige ohne UnsuperChild codiert die Methode seines übergeordneten Elements. Daher hat das Verhalten seiner Methode eingeschränkt, und Unterklassen können keine Funktionalität in die Aufrufkette einfügen.

Der mitsuper ist flexibler. Die Aufrufkette für die Methoden kann abgefangen und die Funktionalität eingefügt werden.

Möglicherweise benötigen Sie diese Funktionalität nicht, aber möglicherweise Unterklassen Ihres Codes.Fazit.

Verwenden Sie immer super, um auf die übergeordnete Klasse zu verweisen, anstatt sie fest zu codieren.

Sie möchten auf die übergeordnete Klasse verweisen, die sich als nächstes in der Zeile befindet, und nicht speziell auf die, von der das Kind erbt.

Wenn Sie super nicht verwenden, kann dies den Benutzern Ihres Codes unnötige Einschränkungen auferlegen.

Not using super can put unnecessary constraints on users of your code.

245
Aaron Hall

Geht das alles nicht davon aus, dass die Basisklasse eine Klasse im neuen Stil ist?

class A:
    def __init__(self):
        print("A.__init__()")

class B(A):
    def __init__(self):
        print("B.__init__()")
        super(B, self).__init__()

Funktioniert nicht in Python 2. class A muss im neuen Stil sein, d. H .: class A(object)

35
mhawke

Ich hatte etwas mit super() gespielt und hatte erkannt, dass wir die Anrufreihenfolge ändern können.

Zum Beispiel haben wir die nächste Hierarchiestruktur:

    A
   / \
  B   C
   \ /
    D

In diesem Fall ist MRO von D (nur für Python 3):

In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

Erstellen wir eine Klasse, in der super() nach der Ausführung der Methode aufgerufen wird.

In [23]: class A(object): #  or with Python 3 can define class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          print("I'm from B")
...:          super().__init__()
...:   
...: class C(A):
...:      def __init__(self):
...:          print("I'm from C")
...:          super().__init__()
...:  
...: class D(B, C):
...:      def __init__(self):
...:          print("I'm from D")
...:          super().__init__()
...: d = D()
...:
I'm from D
I'm from B
I'm from C
I'm from A

    A
   / ⇖
  B ⇒ C
   ⇖ /
    D

Die Auflösungsreihenfolge ist also dieselbe wie in der MRO. Wenn wir jedoch super() am Anfang der Methode aufrufen:

In [21]: class A(object):  # or class A:
...:     def __init__(self):
...:         print("I'm from A")
...:  
...: class B(A):
...:      def __init__(self):
...:          super().__init__()  # or super(B, self).__init_()
...:          print("I'm from B")
...:   
...: class C(A):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from C")
...:  
...: class D(B, C):
...:      def __init__(self):
...:          super().__init__()
...:          print("I'm from D")
...: d = D()
...: 
I'm from A
I'm from C
I'm from B
I'm from D

Wir haben eine andere Reihenfolge, es ist eine umgekehrte Reihenfolge des MRO-Tupels.

    A
   / ⇘
  B ⇐ C
   ⇘ /
    D 

Für zusätzliche Lektüre würde ich die nächsten Antworten empfehlen:

  1. C3-Linearisierungsbeispiel mit Super (eine große Hierarchie)
  2. Wichtige Verhaltensänderungen zwischen alten und neuen Stilklassen
  3. Die Inside-Story über Klassen im neuen Stil
25
Infernion

Wenn Sie super() aufrufen, um die Version einer Klassenmethode, einer Instanzmethode oder einer statischen Methode eines Elternteils aufzulösen, möchten wir die aktuelle Klasse übergeben, in deren Gültigkeitsbereich wir uns als erstes Argument befinden, um anzugeben, in welchen Bereich des Elternteils wir versuchen zu lösen und Als zweites Argument das Objekt von Interesse, um anzugeben, auf welches Objekt wir diesen Geltungsbereich anwenden möchten.

Betrachten Sie eine Klassenhierarchie A, B und C, wobei jede Klasse die übergeordnete Klasse der nachfolgenden Klasse ist und a, b und c die jeweiligen Instanzen jeder Klasse.

super(B, b) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to b, as if b was an instance of A

super(C, c) 
# resolves to the scope of C's parent i.e. B
# and applies that scope to c

super(B, c) 
# resolves to the scope of B's parent i.e. A 
# and applies that scope to c

super mit einer statischen Methode verwenden

z.B. mit super() innerhalb der __new__()-Methode

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return super(A, cls).__new__(cls, *a, **kw)

Erläuterung: 

1- Auch wenn __new__() normalerweise einen Verweis auf die aufrufende Klasse als ersten Parameter verwendet, wird nicht in Python als Klassenmethode implementiert, sondern als statische Methode. Das heißt, ein Verweis auf eine Klasse muss explizit als erstes Argument übergeben werden, wenn __new__() direkt aufgerufen wird:

# if you defined this
class A(object):
    def __new__(cls):
        pass

# calling this would raise a TypeError due to the missing argument
A.__new__()

# whereas this would be fine
A.__new__(A)

2- Wenn Sie super() aufrufen, um zur übergeordneten Klasse zu gelangen, übergeben wir die untergeordnete Klasse A als erstes Argument. Dann übergeben wir eine Referenz auf das interessierende Objekt. In diesem Fall wird die Klassenreferenz übergeben, wenn A.__new__(cls) aufgerufen wurde. In den meisten Fällen handelt es sich auch um einen Verweis auf die Kindklasse. In einigen Situationen ist dies möglicherweise nicht der Fall, zum Beispiel bei der Vererbung mehrerer Generationen.

super(A, cls)

3- da __new__() eine statische Methode ist, gibt super(A, cls).__new__ auch eine statische Methode aus und muss alle Argumente explizit angegeben werden, einschließlich des Verweises auf das Objekt von insterest, in diesem Fall cls.

super(A, cls).__new__(cls, *a, **kw)

4- dasselbe tun ohne super

class A(object):
    def __new__(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        return object.__new__(cls, *a, **kw)

Verwendung von super mit einer Instanzmethode

z.B. mit super() innerhalb von __init__()

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        super(A, self).__init__(*a, **kw)

Erläuterung:

1- __init__ ist eine Instanzmethode, was bedeutet, dass als erstes Argument eine Referenz auf eine Instanz genommen wird. Wenn der Verweis direkt von der Instanz aufgerufen wird, wird die Referenz implizit übergeben, dh Sie müssen sie nicht angeben:

# you try calling `__init__()` from the class without specifying an instance
# and a TypeError is raised due to the expected but missing reference
A.__init__() # TypeError ...

# you create an instance
a = A()

# you call `__init__()` from that instance and it works
a.__init__()

# you can also call `__init__()` with the class and explicitly pass the instance 
A.__init__(a)

2- Beim Aufruf von super() in __init__() übergeben wir die Kindklasse als erstes Argument und das Objekt von Interesse als zweites Argument, das im Allgemeinen eine Referenz auf eine Instanz der Kindklasse ist.

super(A, self)

3- Der Aufruf super(A, self) gibt einen Proxy zurück, der den Gültigkeitsbereich auflöst und auf self anwendet, als wäre es jetzt eine Instanz der übergeordneten Klasse. Nennen wir diesen Proxy s. Da __init__() eine Instanzmethode ist, übergibt der Aufruf s.__init__(...) implizit eine Referenz von self als erstes Argument an __init__() des übergeordneten Objekts.

4- Um dasselbe ohne super zu tun, müssen wir explizit einen Verweis auf eine Instanz an die Version von __init__() des übergeordneten Objekts übergeben.

class A(object): 
    def __init__(self, *a, **kw):
        # ...
        # you make some changes here
        # ...

        object.__init__(self, *a, **kw)

super mit einer Klassenmethode verwenden

class A(object):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        print "A.alternate_constructor called"
        return cls(*a, **kw)

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return super(B, cls).alternate_constructor(*a, **kw)

Erläuterung:

1- Eine Klassenmethode kann direkt von der Klasse aus aufgerufen werden und nimmt als ersten Parameter einen Verweis auf die Klasse. 

# calling directly from the class is fine,
# a reference to the class is passed implicitly
a = A.alternate_constructor()
b = B.alternate_constructor()

2- Wenn Sie super() innerhalb einer Klassenmethode aufrufen, um sie auf die Version ihres übergeordneten Elements aufzulösen, möchten wir die aktuelle untergeordnete Klasse als erstes Argument übergeben, um anzugeben, in welchem ​​Bereich des übergeordneten Objekts wir versuchen, das aufzulösen, und das interessierende Objekt als zweites Argument, um anzugeben, auf welches Objekt wir diesen Gültigkeitsbereich anwenden möchten. Dies ist im Allgemeinen eine Referenz auf die untergeordnete Klasse selbst oder eine ihrer Unterklassen.

super(B, cls_or_subcls)

3- Der Aufruf super(B, cls) löst sich in den Gültigkeitsbereich von A auf und wendet ihn auf cls an. Da alternate_constructor() eine Klassenmethode ist, wird der Aufruf super(B, cls).alternate_constructor(...) implizit eine Referenz von cls als erstes Argument an As Version von alternate_constructor() übergeben.

super(B, cls).alternate_constructor()

Wenn Sie dasselbe tun, ohne super() zu verwenden, müssen Sie einen Verweis auf die unbound - Version von A.alternate_constructor() (d. H. Die explizite Version der Funktion) erhalten. Das würde einfach nicht funktionieren:

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        return A.alternate_constructor(cls, *a, **kw)

Das Obige würde nicht funktionieren, da die A.alternate_constructor()-Methode einen impliziten Verweis auf A als erstes Argument verwendet. Die cls, die hier übergeben wird, wäre das zweite Argument.

class B(A):
    @classmethod
    def alternate_constructor(cls, *a, **kw):
        # ...
        # whatever you want to specialize or override here
        # ...

        print "B.alternate_constructor called"
        # first we get a reference to the unbound 
        # `A.alternate_constructor` function 
        unbound_func = A.alternate_constructor.im_func
        # now we call it and pass our own `cls` as its first argument
        return unbound_func(cls, *a, **kw)
15
Michael Ekoka

Viele tolle Antworten, aber für visuelle Lernende: Erst mit Argumenten nach Super suchen lassen, dann ohne. super inheritance tree example

Stellen Sie sich vor, es gibt eine Instanz jack, die aus der Klasse Jack erstellt wurde und deren Vererbungskette im Bild grün dargestellt ist. Berufung:

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

verwendet die MRO (Method Resolution Order) von jack (seinen Vererbungsbaum in einer bestimmten Reihenfolge) und beginnt mit der Suche ab Jack. Warum kann man eine Elternklasse anbieten? Wenn wir von der Instanz jack aus suchen, wird die Instanzmethode gefunden. Der springende Punkt ist, die übergeordnete Methode zu finden.

Wenn man keine Argumente an super liefert, ist es wie das erste Argument die Klasse von self, und das zweite Argument ist self. Diese werden in Python3 automatisch berechnet.

Angenommen, wir möchten die Methode von Jack nicht verwenden, statt Jack zu übergeben, könnten wir Jen übergeben, um die Suche nach der Methode von Jen nach oben zu starten.

Sie durchsucht jeweils eine Ebene (Breite nicht Tiefe), z. Wenn Adam und Sue beide die erforderliche Methode haben, wird zuerst die aus Sue gefunden.

Wenn sowohl Cain als auch Sue die erforderliche Methode hätten, würde die Methode von Cain zuerst aufgerufen. Dies entspricht im Code:

Class Jen(Cain, Sue):

MRO ist von links nach rechts.

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

Das ist ziemlich leicht zu verstehen.

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

Ok, was passiert jetzt, wenn Sie super(Child,self) verwenden?

Wenn eine untergeordnete Instanz erstellt wird, befindet sich ihre MRO (Method Resolution Order) in der Reihenfolge (Child, SomeBaseClass, object), basierend auf der Vererbung. (Angenommen, SomeBaseClass hat keine anderen übergeordneten Objekte außer dem Standardobjekt.)

Durch Übergeben von Child, self sucht super im MRO der self-Instanz und gibt das Proxy-Objekt neben Child zurück. In diesem Fall handelt es sich um SomeBaseClass. Dieses Objekt ruft dann die __init__-Methode von SomeBaseClass auf. Mit anderen Worten, wenn es sich um super(SomeBaseClass,self) handelt, wäre das von super zurückgegebene Proxy-Objekt object 

Bei Multi-Vererbung kann der MRO viele Klassen enthalten. Im Grunde können Sie mit super entscheiden, wo Sie mit der Suche im MRO beginnen möchten.

0
dorisxx

einige gute Antworten hier, aber sie behandeln nicht, wie super() verwendet wird, wenn verschiedene Klassen in der Hierarchie unterschiedliche Signaturen haben ... insbesondere im Fall von __init__

um diesen Teil zu beantworten und in der Lage zu sein, super() effektiv zu verwenden, würde ich vorschlagen, meine Antwort zu lesen super () und die Signatur von kooperativen Methoden zu ändern .

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

  1. die Klassen der obersten Ebene in Ihrer Hierarchie müssen von einer benutzerdefinierten Klasse wie SuperObject erben:
  2. wenn Klassen unterschiedliche Argumente annehmen können, übergeben Sie immer alle Argumente, die Sie erhalten haben, als Schlüsselwortargumente an die Super-Funktion 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.__for cls in mro]}'
            )

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

beispielnutzung:

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
0
Aviad Rozenhek