it-swarm.com.de

Python super () mit __init __ () -Methoden verstehen

Ich versuche die Verwendung von super() zu verstehen. So wie es aussieht, können beide untergeordneten Klassen erstellt werden.

Ich bin gespannt auf den tatsächlichen Unterschied zwischen den folgenden 2 Kinderklassen.

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

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()

ChildA() 
ChildB()
2312
Mizipzor

Mit super() können Sie vermeiden, explizit auf die Basisklasse zu verweisen, die Nice sein kann. Der Hauptvorteil liegt jedoch in der Mehrfachvererbung, bei der alle Arten von lustigen Sachen vorkommen können. Lesen Sie die Standarddokumente unter Super , falls Sie dies noch nicht getan haben.

Beachten Sie, dass die Syntax wurde in Python 3.0 geändert : Sie können nur super().__init__() anstelle von super(ChildB, self).__init__() sagen, was IMO ein bisschen netter ist. In den Standarddokumenten wird auch auf eine Anleitung zur Verwendung von super () verwiesen, die recht erklärend ist.

1676
Kiv

Ich versuche super() zu verstehen

Der Grund, warum wir super verwenden, ist, dass untergeordnete Klassen, die möglicherweise eine kooperative Mehrfachvererbung verwenden, die richtige Funktion der nächsten übergeordneten Klasse in der Reihenfolge der Methodenauflösung (Method Resolution Order, MRO) aufrufen.

In Python 3 können wir es so nennen:

class ChildB(Base):
    def __init__(self):
        super().__init__() 

In Python 2 müssen wir es folgendermaßen verwenden:

super(ChildB, self).__init__()

Ohne Super sind Sie in Ihrer Fähigkeit, Mehrfachvererbung zu verwenden, eingeschränkt:

Base.__init__(self) # Avoid this.

Ich erkläre weiter unten.

"Welchen Unterschied gibt es eigentlich in diesem Code ?:"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super(ChildB, self).__init__()
        # super().__init__() # you can call super like this in Python 3!

Der Hauptunterschied in diesem Code besteht darin, dass Sie im __init__ mit super eine Indirektionsebene erhalten, die anhand der aktuellen Klasse den __init__ der nächsten Klasse ermittelt, um im MRO nachzuschlagen.

Ich veranschauliche diesen Unterschied in einer Antwort auf die Frage Kanonische Frage, wie man 'super' in Python verwendet? , die die Abhängigkeitsinjektion demonstriert und kooperative Mehrfachvererbung .

Wenn Python nicht super hatte

Hier ist der Code, der tatsächlich super entspricht (wie er in C implementiert ist, abzüglich einiger Überprüfungs- und Fallback-Verhaltensweisen und in Python übersetzt):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()             # Get the Method Resolution Order.
        check_next = mro.index(ChildB) + 1 # Start looking after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

Etwas ähnlicher wie Python geschrieben:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

Wenn wir das Objekt super nicht hätten, müssten wir diesen manuellen Code überall schreiben (oder neu erstellen!), Um sicherzustellen, dass wir die richtige nächste Methode in der Reihenfolge der Methodenauflösung aufrufen!

Wie macht super das in Python 3, ohne explizit zu erfahren, von welcher Klasse und Instanz aus die Methode aufgerufen wurde?

Es ruft den aufrufenden Stack-Frame ab und findet die Klasse (implizit als lokale freie Variable __class__ gespeichert, wodurch die aufrufende Funktion einen Abschluss über die Klasse darstellt) und das erste Argument für diese Funktion, bei dem es sich um die Instanz oder handeln sollte Klasse, die darüber informiert, welche Method Resolution Order (MRO) verwendet werden soll.

Da dieses erste Argument für die MRO erforderlich ist, ist die Verwendung von super mit statischen Methoden nicht möglich .

Kritik anderer Antworten:

mit super () können Sie vermeiden, explizit auf die Basisklasse zu verweisen, die Nice sein kann. . Der Hauptvorteil ist jedoch die Mehrfachvererbung, bei der alle möglichen lustigen Dinge passieren können. Sehen Sie sich die Standarddokumentation zu Super an, falls Sie dies noch nicht getan haben.

Es ist ziemlich wellenförmig und sagt uns nicht viel, aber der Punkt von super ist nicht zu vermeiden, die Elternklasse zu schreiben. Es muss sichergestellt werden, dass die nächste Methode in der Reihenfolge der Methodenauflösung (MRO) aufgerufen wird. Dies wird bei der Mehrfachvererbung wichtig.

Ich erkläre es hier.

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super(ChildB, self).__init__()

Und lassen Sie uns eine Abhängigkeit erstellen, die wir nach dem Kind benennen möchten:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super(UserDependency, self).__init__()

Denken Sie jetzt daran, ChildB verwendet super, ChildA nicht:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super(UserA, self).__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super(UserB, self).__init__()

Und UserA ruft die UserDependency-Methode nicht auf:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

Aber UserB, weil ChildBsuper verwendet, tut !:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

Kritik für eine andere Antwort

Unter keinen Umständen sollten Sie Folgendes tun, was eine andere Antwort nahe legt, da Sie definitiv Fehler bekommen, wenn Sie ChildB unterordnen:

super(self.__class__, self).__init__() # Don't do this. Ever.

(Diese Antwort ist weder schlau noch besonders interessant, aber trotz direkter Kritik in den Kommentaren und über 17 Abwertungen bestand der Antwortende darauf, sie vorzuschlagen, bis ein freundlicher Redakteur sein Problem behoben hatte.)

Erläuterung: Diese Antwort schlug vor, super wie folgt anzurufen:

super(self.__class__, self).__init__()

Dies ist völlig falsch. super lässt uns den nächsten Elternteil in der MRO nachschlagen (siehe den ersten Abschnitt dieser Antwort) für Kinderklassen. Wenn Sie super mitteilen, dass wir uns in der Methode der untergeordneten Instanz befinden, wird die nächste Methode in der Zeile (wahrscheinlich diese) gesucht, was zu einer Rekursion führt und wahrscheinlich einen logischen Fehler verursacht (im Beispiel des Antwortenden) oder a RuntimeError wenn die Rekursionstiefe überschritten wird.

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'
617
Aaron Hall

Es wurde festgestellt, dass Sie in Python 3.0+ verwenden können

super().__init__()

um Ihren Anruf zu tätigen, müssen Sie nicht explizit auf die übergeordneten OR Klassennamen verweisen. Dies kann nützlich sein. Ich möchte nur hinzufügen, dass es für Python 2.7 oder darunter möglich ist, dieses namensunempfindliche Verhalten zu erhalten, indem self.__class__ anstelle des Klassennamens geschrieben wird, d. H.

super(self.__class__, self).__init__()

Dies unterbricht jedoch Aufrufe von super für alle Klassen, die von Ihrer Klasse erben, wobei self.__class__ eine untergeordnete Klasse zurückgeben könnte. Zum Beispiel:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

Hier habe ich eine Klasse Square, die eine Unterklasse von Rectangle ist. Angenommen, ich möchte keinen separaten Konstruktor für Square schreiben, weil der Konstruktor für Rectangle gut genug ist, aber aus irgendeinem Grund möchte ich ein Square implementieren, damit ich eine andere Methode erneut implementieren kann.

Wenn ich Square mit mSquare = Square('a', 10,10) erstelle, ruft Python den Konstruktor für Rectangle auf, weil ich Square keinen eigenen Konstruktor gegeben habe. Im Konstruktor für Rectangle gibt der Aufruf super(self.__class__,self) jedoch die Superklasse von mSquare zurück, sodass der Konstruktor erneut für Rectangle aufgerufen wird. So läuft die Endlosschleife ab, wie von @S_C erwähnt. In diesem Fall rufe ich beim Ausführen von super(...).__init__() den Konstruktor für Rectangle auf, aber da ich keine Argumente gebe, erhalte ich eine Fehlermeldung.

248
AnjoMan

Super hat keine Nebenwirkungen

Base = ChildB

Base()

funktioniert wie erwartet

Base = ChildA

Base()

gerät in eine unendliche Rekursion.

80
S C

Nur ein Köpfchen weiter ... mit Python 2.7, und ich glaube, seitdem super() in Version 2.2 eingeführt wurde, können Sie super() nur aufrufen, wenn eines der Elternteile von einer Klasse erbt das erbt schließlich object ( new-style classes ).

Persönlich, wie für python 2.7 Code, werde ich weiterhin BaseClassName.__init__(self, args) verwenden, bis ich tatsächlich den Vorteil habe, super() zu verwenden.

71
rgenito

Wirklich nicht. super() betrachtet die nächste Klasse in der MRO (Reihenfolge der Methodenauflösung, auf die mit cls.__mro__ zugegriffen wird), um die Methoden aufzurufen. Nur die Basis anrufen __init__ ruft die Basis an __init__. Zufällig hat der MRO genau einen Gegenstand - die Basis. Also machen Sie genau das Gleiche, aber auf eine schönere Art und Weise mit super() (insbesondere, wenn Sie später in die Mehrfachvererbung einsteigen).

51

Der Hauptunterschied ist, dass _ChildA.__init___ bedingungslos _Base.__init___ aufruft, während _ChildB.__init____init___ in jeder Klasse aufruft, die zufällig ChildB in selfVARIAB ist Ahnenreihe (die von den Erwartungen abweichen kann).

Wenn Sie ein ClassC hinzufügen, das Mehrfachvererbung verwendet:

_class Mixin(Base):
  def __init__(self):
    print "Mixin stuff"
    super(Mixin, self).__init__()

class ChildC(ChildB, Mixin):  # Mixin is now between ChildB and Base
  pass

ChildC()
help(ChildC) # shows that the the Method Resolution Order is ChildC->ChildB->Mixin->Base
_

dann ist Base NICHT MEHR DAS ÜBERGEORDNETE ELEMENT VON ChildB für ChildC-Instanzen. Jetzt zeigt super(ChildB, self) auf Mixin, wenn self eine ChildC-Instanz ist.

Sie haben Mixin zwischen ChildB und Base eingefügt. Und Sie können es mit super() nutzen

Wenn Sie also Ihre Klassen so gestalten, dass sie in einem kooperativen Mehrfachvererbungsszenario verwendet werden können, verwenden Sie super, da Sie nicht genau wissen, wer zur Laufzeit der Vorfahr sein wird.

Die super als super angesehene Post und pycon 2015 begleitendes Video erklären dies ziemlich gut.

28
ecerulm