Frage Verstehen von Python super () mit __init __ () Methoden [duplizieren]


Diese Frage hat hier bereits eine Antwort:

Ich versuche den Gebrauch von zu verstehen super(). Wie es aussieht, können beide untergeordneten Klassen erstellt werden, einfach gut.

Ich bin neugierig, über den tatsächlichen Unterschied zwischen den folgenden 2 Kinderklassen zu wissen.

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

1979
2018-02-23 00:30


Ursprung


Antworten:


super() lässt Sie vermeiden, explizit auf die Basisklasse zu verweisen, was schön sein kann. Aber der Hauptvorteil kommt mit Mehrfachvererbung, wo alle Arten von lustige Sachen kann passieren. Siehe die Standard-Dokumente auf Super wenn du es nicht schon getan hast.

Beachten Sie, dass Die Syntax wurde in Python 3.0 geändert: Du kannst es einfach sagen super().__init__() Anstatt von super(ChildB, self).__init__() was IMO ist ein bisschen schöner.


1423
2018-02-23 00:37



ich versuche zu verstehen super()

Der Grund, den wir verwenden super ist so, dass untergeordnete Klassen, die kooperative Mehrfachvererbung verwenden, die korrekte nächste Elternklassenfunktion in der Methodenauflösungsreihenfolge (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 wie folgt verwenden:

        super(ChildB, self).__init__()

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

        Base.__init__(self) # Avoid this.

Ich erkläre weiter unten.

"Welchen Unterschied gibt es tatsächlich 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 in der __init__ mit super, die die aktuelle Klasse verwendet, um die nächsten Klassen zu bestimmen __init__ im MRO nachsehen.

Ich illustriere diesen Unterschied in einer Antwort auf die kanonische Frage, Wie benutzt man 'Super' in Python?, die demonstriert Abhängigkeitsspritze und kooperative Mehrfachvererbung.

Wenn Python nicht hatte super

Hier ist Code, der eigentlich genau dem entspricht super (wie es in C implementiert wird, abzüglich einiger Check- und Fallback-Verhalten und übersetzt nach Python):

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 mehr wie natives 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 nicht hätten super Objekt, wir müssten diesen manuellen Code überall schreiben (oder neu erstellen!), um sicherzustellen, dass wir die richtige nächste Methode in der Methodenauflösung aufrufen!

Wie macht Super das in Python 3, ohne explizit angegeben zu werden, welche Klasse und Instanz von der Methode aus aufgerufen wurde?

Es ruft den aufrufenden Stapelrahmen ab und findet die Klasse (implizit als lokale freie Variable gespeichert, __class__(die aufrufende Funktion wird zu einer Schließung über die Klasse gemacht) und das erste Argument zu dieser Funktion, die die Instanz oder Klasse sein sollte, die sie darüber informiert, welche Method Resolution Order (MRO) zu verwenden ist.

Da dieses erste Argument für die MRO erforderlich ist, verwenden super mit statischen Methoden ist unmöglich.

Kritik an anderen Antworten:

Mit super () können Sie vermeiden, explizit auf die Basisklasse zu verweisen, was nett sein kann. . Der Hauptvorteil ist jedoch die Mehrfachvererbung, bei der alle möglichen Arten von Spaß passieren können. Sehen Sie sich die Standarddokumente auf Super an, wenn Sie dies nicht bereits getan haben.

Es ist eher hand-wavey und sagt uns nicht viel, aber der Punkt von super ist es nicht zu vermeiden, die Elternklasse zu schreiben. Der Punkt besteht darin, sicherzustellen, dass die nächste Methode in der Reihenfolge der Methodenauflösung (MRO) aufgerufen wird. Dies wird bei der Mehrfachvererbung wichtig.

Ich werde es hier erklären.

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 erstellen wir eine Abhängigkeit, die nach dem Kind aufgerufen werden soll:

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

Nun erinnere dich, ChildB nutzt 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 ChildB Verwendet super, 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 nahelegt, da Sie auf jeden Fall Fehler bekommen werden, wenn Sie ChildB ableiten:

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

(Diese Antwort ist nicht klug oder besonders interessant, aber trotz der direkten Kritik in den Kommentaren und über 17 downvotes, beharrte der Antworter darauf, es vorzuschlagen, bis ein freundlicher Redakteur sein Problem behob.)

Erklärung: Diese Antwort schlug vor, Super so zu nennen:

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

Das ist vollständig falsch. super Lassen Sie uns den nächsten Elternteil im MRO nachschlagen (siehe den ersten Abschnitt dieser Antwort) für Unterklassen. Wenn du erzählst super Wir befinden uns in der Methode der Child-Instanz, dann sucht sie die nächste Methode in der Zeile (wahrscheinlich diese), was zu Rekursion führt, was wahrscheinlich zu einem logischen Fehler führt (im Beispiel des Antworters) 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'

407
2017-11-25 19:00



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

super().__init__() 

um Ihren Anruf zu tätigen, der knapp ist und nicht erfordert, dass Sie explizit auf die Eltern- oder Klassennamen verweisen, was praktisch sein kann. Ich möchte nur hinzufügen, dass für Python 2.7 oder darunter, ist es möglich, dieses namensunempfindliche Verhalten durch Schreiben zu bekommen self.__class__ anstelle des Klassennamens, d.h.

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

ABER, dies bricht Anrufe zu super für alle Klassen, die von Ihrer Klasse erben, wo self.__class__ könnte eine Kindklasse zurückgeben. Beispielsweise:

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, das ist eine Unterklasse von Rectangle. Angenommen, ich möchte keinen separaten Konstruktor schreiben Square weil der Konstruktor für Rectangle ist gut genug, aber aus welchem ​​Grund auch immer möchte ich ein Quadrat implementieren, so dass ich eine andere Methode neu implementieren kann.

Wenn ich ein Square verwenden mSquare = Square('a', 10,10), Python ruft den Konstruktor für Rectangle weil ich nicht gegeben habe Square ein eigener Konstruktor. Im Konstruktor für Rectangle, der Anruf super(self.__class__,self) wird die Oberklasse von zurückkehren mSquare, so ruft es den Konstruktor für Rectangle nochmal. So geschieht die Endlosschleife, wie von @S_C erwähnt. In diesem Fall, wenn ich laufe super(...).__init__() Ich rufe den Konstruktor an Rectangle aber da ich keine Argumente gebe, werde ich einen Fehler bekommen.


221
2017-10-08 20:08



Super hat keine Nebenwirkungen

Base = ChildB

Base()

funktioniert wie erwartet

Base = ChildA

Base()

gerät in unendliche Rekursion.


75
2017-11-27 23:26



Nur ein Kopf ... mit Python 2.7, und ich glaube seitdem super() wurde in Version 2.2 eingeführt, Sie können nur anrufen super()wenn einer der Eltern von einer Klasse erbt, die schließlich erbt object (neue Klassen).

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


68
2018-05-25 17:52



Es gibt nicht, wirklich. super() schaut auf die nächste Klasse im MRO (Reihenfolge der Methodenauflösung, die mit aufgerufen wird cls.__mro__) um die Methoden aufzurufen. Einfach die Basis anrufen __init__ ruft die Basis an __init__. Zufällig hat das MRO genau einen Gegenstand - die Basis. Du machst also genau dasselbe, aber auf eine schönere Art mit super() (besonders wenn Sie später in die Mehrfachvererbung kommen).


45
2018-02-23 00:34



Der Hauptunterschied ist, dass ChildA.__init__ wird bedingungslos anrufen Base.__init__ wohingegen ChildB.__init__ werde anrufen __init__ im was auch immer Klasse ist ChildB Vorfahre in selfdie Linie der Vorfahren (was von dem abweichen kann, was Sie erwarten).

Wenn Sie ein hinzufügen ClassC das verwendet mehrfache Vererbung:

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 Base ist nicht länger das Elternteil von ChildB zum ChildC Instanzen. Jetzt super(ChildB, self) wird darauf zeigen Mixin ob self ist ein ChildC Beispiel.

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

Wenn Sie also Ihre Klassen so entworfen haben, dass sie in einem Cooperative-Multiple-Vererbung-Szenario verwendet werden können, verwenden Sie super weil Sie nicht wirklich wissen, wer zur Laufzeit der Vorfahre sein wird.

Das Super angesehener Super Post und pycon 2015 begleitendes Video erkläre das ziemlich gut.


22
2017-09-21 06:41