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.

465voto

Was ist der Unterschied?

SomeBaseClass.__init__(self) 

bedeutet anrufen SomeBaseClass 's __init__ . während

super().__init__()

bedeutet, dass man eine Bindung aufruft __init__ von der übergeordneten Klasse, die folgt SomeBaseClass Kindklasse der Instanz (die diese Methode definiert) in der Methodenauflösungsreihenfolge (MRO) der Instanz.

Wenn die Instanz eine Unterklasse von este Kindklasse, kann es ein anderes Elternteil geben, das in der MRO als nächstes kommt.

Einfach erklärt

Wenn Sie eine Klasse schreiben, möchten Sie, dass andere Klassen sie verwenden können. super() macht es für andere Klassen einfacher, die von Ihnen geschriebene Klasse zu verwenden.

Wie Bob Martin sagt, erlaubt es eine gute Architektur, die Entscheidungsfindung so lange wie möglich hinauszuzögern.

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

Wenn eine andere Klasse die von Ihnen geschriebene Klasse untergliedert, könnte sie auch von anderen Klassen erben. Und diese Klassen könnten eine __init__ das danach kommt __init__ auf der Grundlage der Reihenfolge der Klassen für die Methodenauflösung.

Ohne super würden Sie wahrscheinlich das Elternteil der Klasse, die Sie schreiben, hart kodieren (wie im Beispiel). Dies würde bedeuten, dass Sie nicht die nächste __init__ in der MRO, so dass Sie den darin enthaltenen Code nicht wiederverwenden können.

Wenn Sie Ihren eigenen Code für den persönlichen Gebrauch schreiben, ist Ihnen diese Unterscheidung vielleicht egal. Wenn Sie jedoch möchten, dass andere Ihren Code verwenden, sollten Sie super ist eine Sache, die den Nutzern 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__()

Sie funktioniert ohne Argumente, indem sie im Stack-Frame nach oben geht und das erste Argument der Methode erhält (normalerweise self für eine Instanzmethode oder cls für eine Klassenmethode - könnte aber auch ein anderer Name sein) und das Auffinden der Klasse (z. B. Child ) in den freien Variablen (sie wird unter dem Namen __class__ als freie Abschlussvariable in der Methode).

Ich habe es vorgezogen, die kompatiblen Möglichkeiten der Verwendung von super aber da Python 2 nun weitgehend veraltet ist, werde ich die Python-3-Methode demonstrieren, d.h. den Aufruf von super ohne Argumente.

Indirektion mit Vorwärtskompatibilität

Was haben Sie davon? Bei Einfachvererbung sind die Beispiele aus der Frage aus Sicht der statischen Analyse praktisch identisch. Allerdings ist die Verwendung von super bietet Ihnen eine indirekte Ebene mit Vorwärtskompatibilität.

Für erfahrene Entwickler ist die Vorwärtskompatibilität sehr wichtig. Sie möchten, dass Ihr Code mit minimalen Änderungen weiter funktioniert, wenn Sie ihn ändern. Wenn Sie sich Ihren Revisionsverlauf ansehen, möchten Sie genau sehen, was wann geändert wurde.

Sie können mit Einfachvererbung beginnen, aber wenn Sie sich entscheiden, eine weitere Basisklasse hinzuzufügen, müssen Sie nur die Zeile mit den Basen ändern - wenn sich die Basen in einer Klasse ändern, von der Sie erben (z. B. wenn ein Mixin hinzugefügt wird), ändern Sie nichts in dieser Klasse.

In Python 2 werden die Argumente für super und die korrekten Methodenargumente können ein wenig verwirrend sein, daher schlage ich vor, nur die Python 3-Methode für den Aufruf zu verwenden.

Wenn Sie wissen, dass Sie Folgendes verwenden super korrekt mit einfacher Vererbung, das macht die Fehlersuche in Zukunft weniger schwierig.

Injektion von Abhängigkeiten

Andere Personen können Ihren Code verwenden und Eltern in die Methodenauflösung einfügen:

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().__init__()

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

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

class UnsuperInjector(UnsuperChild, InjectMe): pass

class SuperInjector(SuperChild, InjectMe): pass

Bei Verwendung des Un-Super-Kindes wird die Abhängigkeit nicht injiziert, da das Kind, das Sie verwenden, die Methode fest kodiert hat, um nach seiner eigenen Methode aufgerufen zu werden:

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

Allerdings wird die Klasse mit dem Kind, das super kann die Abhängigkeit korrekt injizieren:

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

Adressierung eines Kommentars

Warum in aller Welt sollte das nützlich sein?

Python linearisiert einen komplizierten Vererbungsbaum über die C3-Linearisierungsalgorithmus um einen Methodenauflösungsauftrag (MRO) zu erstellen.

Wir wollen, dass Methoden nachgeschlagen werden in dieser Reihenfolge .

Für eine Methode, die in einer übergeordneten Methode definiert ist, um die nächste Methode in dieser Reihenfolge zu finden, ohne super müsste es

  1. das mro aus dem Typ der Instanz holen
  2. Suchen Sie nach dem Typ, der die Methode definiert
  3. finden Sie den nächsten Typ mit der Methode
  4. diese Methode binden und mit den erwarteten Argumenten aufrufen

En UnsuperChild sollten keinen Zugang haben zu InjectMe . Warum lautet die Schlussfolgerung nicht "Vermeiden Sie immer die Verwendung von super "? Was übersehe ich hier?

En UnsuperChild tut no Zugang haben zu InjectMe . Es ist die UnsuperInjector die Zugang hat zu InjectMe - und kann dennoch die Methode dieser Klasse nicht über die Methode aufrufen, von der sie erbt UnsuperChild .

Beide Child-Klassen beabsichtigen, eine gleichnamige Methode aufzurufen, die in der MRO als Nächstes kommt, z. B. eine andere Klasse, die ihm bei seiner Erstellung nicht bekannt war.

Derjenige ohne super die Methode ihrer Eltern fest kodiert - und damit das Verhalten ihrer Methode eingeschränkt hat, und Unterklassen keine Funktionalität in die Aufrufkette einspeisen können.

Die eine mit super hat eine größere Flexibilität. Die Aufrufkette für die Methoden kann abgefangen und Funktionalität injiziert werden.

Sie brauchen diese Funktionalität vielleicht nicht, aber die Unterklassen Ihres Codes schon.

Schlussfolgerung

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

Was Sie beabsichtigen, ist ein Verweis auf die übergeordnete Klasse, die die nächste in der Reihe ist, und nicht speziell auf die Klasse, von der Sie das Kind erben sehen.

Nicht verwenden super kann den Nutzern Ihres Codes unnötige Beschränkungen auferlegen.

355voto

John Millikin Punkte 190278

Die Vorteile von super() Die Vorteile der Einfachvererbung sind minimal: Sie müssen den Namen der Basisklasse nicht in jede Methode, die ihre übergeordneten Methoden verwendet, fest einprogrammieren.

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

61voto

skhalymon Punkte 1854

Ich hatte ein bisschen mit super() und hatte erkannt, dass wir die Aufrufreihenfolge ändern können.

Wir haben zum Beispiel folgende Hierarchiestruktur:

    A
   / \
  B   C
   \ /
    D

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

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

Lassen Sie uns eine Klasse erstellen, in der super() Aufrufe nach der Methodenausführung.

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

Wir sehen also, dass die Auflösungsreihenfolge dieselbe ist wie in MRO. Aber wenn wir aufrufen super() am Anfang der Methode:

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 

Als zusätzliche Lektüre empfehle ich die nächsten Antworten:

  1. C3-Linearisierungsbeispiel mit Super (eine große Hierarchie)
  2. Wichtige Verhaltensänderungen zwischen alten und neuen Stilklassen
  3. Die Insider-Geschichte über New-Style-Klassen

36voto

mhawke Punkte 79590

Setzt all dies nicht voraus, dass die Basisklasse eine Klasse des neuen Stils ist?

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

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

Wird in Python 2 nicht funktionieren. class A muss new-style sein, d.h: class A(object)

27voto

Michael Ekoka Punkte 17537

Bei einem Anruf super() um eine Klassenmethode, Instanzmethode oder statische Methode in die Version eines Elternteils aufzulösen, müssen wir die aktuelle Klasse, in deren Bereich wir uns befinden, als erstes Argument übergeben, um anzugeben, in welchen Bereich des Elternteils wir aufzulösen versuchen, und als zweites Argument das Objekt von Interesse, um anzugeben, auf welches Objekt wir versuchen, diesen Bereich anzuwenden.

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

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

Verwendung von super mit einer statischen Methode

z.B. mit super() aus dem __new__() método

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 es üblich ist, dass __new__() als ersten Parameter einen Verweis auf die aufrufende Klasse zu nehmen, ist es no in Python als classmethod implementiert, sondern als staticmethod. Das heißt, ein Verweis auf eine Klasse muss explizit als erstes Argument übergeben werden, wenn man __new__() direkt:

# 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- beim Aufruf super() um zur übergeordneten Klasse zu gelangen, übergeben wir die untergeordnete Klasse A als erstes Argument, dann übergeben wir einen Verweis auf das Objekt von Interesse, in diesem Fall ist es der Klassenverweis, der übergeben wurde, als A.__new__(cls) genannt wurde. In den meisten Fällen handelt es sich dabei auch um einen Verweis auf die Kindklasse. In manchen Situationen ist dies nicht der Fall, z. B. bei der Vererbung über mehrere Generationen.

super(A, cls)

3- da in der Regel __new__() ist eine statische Methode, super(A, cls).__new__ gibt ebenfalls eine statische Methode zurück und muss explizit mit allen Argumenten versehen werden, einschließlich des Verweises auf das betreffende Objekt, in diesem Fall cls .

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

4- das Gleiche 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() von innen __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 sie als erstes Argument einen Verweis auf eine Instanz annimmt. Wenn sie direkt von der Instanz aufgerufen wird, wird der Verweis implizit übergeben, d.h. Sie müssen ihn 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 super() innerhalb __init__() übergeben wir die Kindklasse als erstes Argument und das gewünschte Objekt als zweites Argument, das im Allgemeinen ein Verweis auf eine Instanz der Kindklasse ist.

super(A, self)

3- Der Anruf super(A, self) gibt einen Proxy zurück, der den Bereich auflöst und ihn auf self als ob es jetzt eine Instanz der übergeordneten Klasse wäre. Nennen wir diesen Proxy s . Seit __init__() eine Instanzmethode ist, wird der Aufruf s.__init__(...) wird implizit eine Referenz von self als erstes Argument für die übergeordnete __init__() .

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

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

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

Verwendung von super mit einer Klassenmethode

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 erhält 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- beim Aufruf super() innerhalb einer Klassenmethode auf die Version des Elternteils aufzulösen, wollen wir die aktuelle Kindklasse als erstes Argument übergeben, um anzugeben, auf welchen Bereich des Elternteils wir auflösen wollen, und das Objekt von Interesse als zweites Argument, um anzugeben, auf welches Objekt wir diesen Bereich anwenden wollen, was im Allgemeinen eine Referenz auf die Kindklasse selbst oder eine ihrer Unterklassen ist.

super(B, cls_or_subcls)

3- Der Anruf super(B, cls) löst sich auf in den Geltungsbereich von A und wendet sie an auf cls . Seit alternate_constructor() eine Klassenmethode ist, wird der Aufruf super(B, cls).alternate_constructor(...) wird implizit eine Referenz von cls als erstes Argument für A Die Version von alternate_constructor()

super(B, cls).alternate_constructor()

4-, um dasselbe zu tun, ohne super() müssen Sie einen Verweis auf die Ungebunden Version von A.alternate_constructor() (d.h. die explizite Version der Funktion). Dies einfach zu tun, würde 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)

Dies würde nicht funktionieren, da die A.alternate_constructor() Methode nimmt einen impliziten Verweis auf A als sein erstes Argument. Die Website cls das 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)

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