Was bedeutet 'super' in Python tun?
-
03-07-2019 - |
Frage
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 super
gesehen eine ganze Menge in Klassen mit nur einfacher Vererbung wird verwendet. Ich kann sehen, warum Sie es in Mehrfachvererbung verwenden würde, aber ich bin nicht klar, was die Vorteile sind es in dieser Situation verwendet wird.
Lösung
Die Vorteile von super()
in Single-Vererbung sind minimal - meist, Sie müssen nicht zu hart Code den Namen der Basisklasse in jede Methode, die ihre Mutter Methoden verwendet,
Allerdings ist es fast unmöglich, Mehrfachvererbung ohne super()
zu verwenden. Dazu gehören gemeinsame Idiome wie Mixins, Schnittstellen, abstrakte Klassen, etc. Dies, um Code erweitert, die Ihnen später erweitert. Wenn jemand später wollte eine Klasse schreiben, die Child
und eine mixin verlängert, würde ihr Code nicht richtig funktionieren.
Andere Tipps
Was ist der Unterschied?
SomeBaseClass.__init__(self)
bedeutet SomeBaseClass
des __init__
zu nennen. während
super(Child, self).__init__()
bedeutet ein gebundenes __init__
von der Elternklasse aufzurufen, die Child
in der Instanz Methode Resolution Order (MRO) folgt.
Wenn die Instanz eine Unterklasse des Kindes ist, kann es ein anderer Elternteil sein, die in der MRO als nächstes kommt.
einfach erklärt
Wenn Sie eine Klasse schreiben, mögen Sie andere Klassen der Lage sein, es zu benutzen. super()
macht es einfacher für andere Klassen, die Klasse verwenden Sie schreiben.
Wie Bob Martin sagt, eine gute Architektur ermöglicht es Ihnen, Entscheidungsfindung so lange wie möglich zu verschieben.
super()
kann diese Art von Architektur ermöglichen.
Wenn eine andere Klasse, die Klasse Unterklassen Sie schrieb, könnte es auch aus anderen Klassen erben. Und diese Klassen könnten eine __init__
haben, die für die Methode Auflösung basierend auf der Reihenfolge der Klassen nach dieser __init__
kommt.
Ohne super
würden Sie wahrscheinlich hart Code die Eltern der Klasse Sie schreiben (wie das Beispiel der Fall ist). Dies würde bedeuten, dass Sie sich den nächsten __init__
im MRO nicht nennen, und Sie erhalten würden somit nicht den Code in ihm wieder zu verwenden.
Wenn Sie Ihren eigenen Code für den persönlichen Gebrauch zu schreiben, können Sie diesen Unterschied nicht. Aber wenn Sie andere wollen Ihren Code verwenden, super
verwendet, ist eine Sache, die für die Nutzer des Codes mehr Flexibilität ermöglicht.
Python 2 im Vergleich zu 3
Dies funktioniert in Python 2 und 3:
super(Child, self).__init__()
Dies funktioniert nur in Python 3:
super().__init__()
Es ohne Argumente wirkt im Stapelrahmen und immer das erste Argumente der Methode nach oben bewegt (in der Regel für eine Instanz-Methode oder self
für eine Klassenmethode cls
- könnte aber andere Namen sein) und die Suche nach der Klasse (zB Child
) in den freien Variablen (es wird nachgeschlagen mit dem Namen __class__
als freier Verschluss Variable in der Methode).
Ich ziehe den Quer kompatibel Art der Verwendung super
zu demonstrieren, aber wenn Sie nur Python 3 verwenden, können Sie es ohne Argumente aufrufen.
Indirection mit Vorwärts-Kompatibilität
Was bedeutet es Ihnen? Für die einfache Vererbung sind die Beispiele aus der Frage praktisch identisch aus einer statischen Analyse Sicht. Allerdings super
Verwendung gibt Ihnen eine Schicht von Dereferenzierung mit Vorwärtskompatibilität.
Aufwärtskompatibilität ist sehr wichtig für erfahrene Entwickler. Sie wollen Ihren Code mit minimalen Änderungen weiter zu arbeiten, wie Sie es ändern. Wenn Sie an Ihrer Revision Geschichte betrachten, wollen Sie genau sehen, was, wenn geändert.
Sie können mit einfacher Vererbung beginnen, aber wenn Sie eine andere Basisklasse hinzufügen entscheiden, Sie müssen nur mit den Basen der Linie ändern - wenn die Basen in einer Klasse ändern erben Sie von (sagen wir ein mixin hinzugefügt wird) Sie Würd nichts in dieser Klasse ändern. Vor allem in Python 2, um die Argumente immer super
und die richtige Methode Argumente richtig schwierig sein. Wenn Sie wissen, dass Sie super
richtig mit einfacher Vererbung verwenden, das macht das Debuggen weniger schwer vorwärts gehen.
Dependency Injection
Andere Personen können Sie Ihren Code und zu injizieren Eltern in die Methode Auflösung verwenden:
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__()
Sagen Sie bitte eine andere Klasse zu Ihrem Objekt hinzufügen, und wollen eine Klasse zwischen Foo und Bar (zum Testen oder aus einem anderen Grund) injizieren:
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
Mit dem un-Super Kind schlägt die Abhängigkeit injizieren, weil das Kind verwenden Sie hartcodiert die Methode muss nach seinem eigenen aufgerufen werden:
>>> o = UnsuperInjector()
UnsuperChild.__init__(self) called
SomeBaseClass.__init__(self) called
Allerdings ist die Klasse mit dem Kind, das super
verwendet, kann die Abhängigkeit richtig injizieren:
>>> o2 = SuperInjector()
SuperChild.__init__(self) called
InjectMe.__init__(self) called
SomeBaseClass.__init__(self) called
Addressing ein Kommentar
Warum in der Welt würde dies nützlich sein?
Python linearisiert einen komplizierten Vererbungsbaum über das C3 Linearisierungsalgorithmus eine Methode Auflösung zu schaffen ( MRO).
Wir wollen Methoden nachgeschlagen werden in dieser Reihenfolge .
Für ein Verfahren in einem Elternteil definiert die nächste in dieser Reihenfolge ohne super
zu finden, wäre es zu
- erhalten die MRO von der Instanz Typ
- Look für den Typ, der die Methode definiert
- Sie den nächsten Typen mit der Methode
- diese Methode binden und sie mit den erwarteten Argumente nennen
Die
UnsuperChild
sollte nicht den Zugang zuInjectMe
haben. Warum ist nicht der Schluss zu „vermeiden Immersuper
mit“? Was ich hier fehlt?
Die UnsuperChild
tun nicht Zugang zu InjectMe
hat. Es ist die UnsuperInjector
, die Zugriff auf InjectMe
hat - und doch nicht auf diese Klasse Methode aus dem Verfahren aus UnsuperChild
erbt aufrufen können.
Die beiden Kinderklassen beabsichtigen, ein Verfahren, mit dem gleichen Namen zu nennen, die in den MRO nächsten kommt, die möglicherweise andere Klasse es nicht bewusst war, als es erstellt wurde.
Das eine ohne super
hart Codes seiner Eltern Methode -. So hat das Verhalten seiner Methode beschränkt, und Subklassen kann nicht Funktionalität in der Aufrufkette injizieren
Die eine mit super
hat eine größere Flexibilität. Die Verbindungskette für die Methoden abgefangen werden kann und Funktionalität injiziert.
Sie können nicht diese Funktionalität benötigen, aber subclassers des Codes kann.
Fazit
Immer super
verwenden die übergeordnete Klasse statt Hartcodierung es zu verweisen.
Was Sie beabsichtigen, ist die übergeordnete Klasse zu verweisen, die nächsten in-line ist, nicht speziell die, die Sie das Kind sehen zu erben von.
Nicht super
verwenden, können unnötige Einschränkungen für Benutzer des Codes setzen.
Ist dies nicht alle davon ausgehen, dass die Basisklasse eine neuen Stil-Klasse ist?
class A:
def __init__(self):
print("A.__init__()")
class B(A):
def __init__(self):
print("B.__init__()")
super(B, self).__init__()
Werde nicht 2. class A
in Python arbeiten müssen neuen Stil sein, das heißt: class A(object)
ich ein wenig mit super()
gespielt hatte, und hatte erkannt, dass wir fordern, um zu ändern.
Zum Beispiel, wir haben nächste Hierarchiestruktur:
A
/ \
B C
\ /
D
In diesem Fall MRO von D wird (für Python nur 3) sein:
In [26]: D.__mro__
Out[26]: (__main__.D, __main__.B, __main__.C, __main__.A, object)
Lassen Sie uns eine Klasse erstellen, wo super()
nach Ausführung der Methode aufruft.
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
So können wir die Auflösung ist, um zu sehen gleiche wie in MRO. Aber wenn wir super()
am Anfang des Verfahrens nennen:
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 eine Reihenfolge des MRO Tupel umgekehrt wird.
A
/ ⇘
B ⇐ C
⇘ /
D
Für weitere Lesung würde ich nächste Antworten empfehlen:
Wenn super()
zu einem Eltern Version eines Class, Instanzmethode oder static zu lösen fordern, wollen wir die aktuelle Klasse, deren Umfang wir sind in als erstes Argument übergeben, um anzuzeigen, welche Eltern Umfang wir versuchen zu lösen zu, und als zweites Argument das Objekt von Interesse, um anzuzeigen, welches Objekt wir diesen Umfang zu.
Betrachten wir eine Klassenhierarchie A
, B
und C
wobei jede Klasse der Elternteil des einen ist es folgende, und a
, b
und c
jeweiligen Instanzen der einzelnen.
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
Mit super
mit einem static
z. mit super()
aus 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)
Erklärung:
1- obwohl es üblich ist für __new__()
als erstes param einen Verweis auf die Berufung Klasse zu nehmen, ist es nicht implementiert in Python als Class, sondern ein static. Das heißt, hat ein Verweis auf eine Klasse explizit als erstes Argument übergeben wird beim Aufruf __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- wenn super()
Aufruf zu der übergeordneten Klasse bekommen wir das Kind Klasse A
als erstes Argument übergeben, dann geben wir eine Referenz auf das Objekt von Interesse, in diesem Fall ist es die Klassenreferenz, die vergangen war, als A.__new__(cls)
genannt wurde . In den meisten Fällen kommt es auch ein Verweis auf das Kind Klasse. In manchen Situationen kann es nicht sein, zum Beispiel im Fall von mehrere Generation Erbschaften.
super(A, cls)
3-, da sie in der Regel __new__()
ein static ist, super(A, cls).__new__
wird auch eine static zurückkehren und muss alle Argumente explizit geliefert werden, einschließlich der Referenz auf das Objekt von insterest, in diesem Fall cls
.
super(A, cls).__new__(cls, *a, **kw)
4- dasselbe ohne super
tun
class A(object):
def __new__(cls, *a, **kw):
# ...
# whatever you want to specialize or override here
# ...
return object.__new__(cls, *a, **kw)
Mit super
mit einer Instanzmethode
z. mit super()
aus __init__()
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
super(A, self).__init__(*a, **kw)
Erklärung:
1- __init__
ist eine Instanzmethode, was bedeutet, dass sie als erstes Argument zu einer Instanz eine Referenz nimmt. Wenn direkt von der Instanz aufgerufen, die Referenz implizit übergeben wird, dh Sie müssen es 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__()
sie das Kind Klasse als erstes Argument und das interessierende Objekt als zweites Argument übergeben, die in der Regel ist ein Verweis auf eine Instanz der Kind Klasse.
super(A, self)
3- Der Aufruf super(A, self)
gibt einen Proxy, der den Umfang und die Anwendung wird es lösen self
, als ob es nun eine Instanz der übergeordneten Klasse. Lassen Sie uns diesen Proxy s
nennen. Da __init__()
eine Instanzmethode ist, wird der Anruf s.__init__(...)
implizit einen Verweis von self
als erstes Argument an die __init__()
der Eltern übergeben.
4-, das gleiche zu tun, ohne super
brauchen wir einen Verweis auf eine Instanz, um die Version der Eltern von __init__()
explizit zu übergeben.
class A(object):
def __init__(self, *a, **kw):
# ...
# you make some changes here
# ...
object.__init__(self, *a, **kw)
Mit super
mit einem Class
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)
Erklärung:
1- A Class kann aus der Klasse aufgerufen werden direkt 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- beim Aufruf super()
innerhalb eines Class seiner Eltern Version davon zu lösen, wollen wir die aktuelle Kind Klasse als erstes Argument übergeben, um anzuzeigen, welche Eltern Umfang wir zu lösen sind, und lassen den Gegenstand des Interesses als das zweite Argument, um anzuzeigen, welches Objekt wir diesen Umfang zu, die im allgemeinen ist ein Verweis auf das Kind Klasse anwenden möchtenselbst oder einer ihrer Unterklassen.
super(B, cls_or_subcls)
3- Der Aufruf super(B, cls)
löst den Umfang der A
und wendet sie auf cls
. Da alternate_constructor()
ein Class ist, wird der Anruf super(B, cls).alternate_constructor(...)
implizit einen Verweis von cls
als erstes Argument zu A
Version von alternate_constructor()
geben
super(B, cls).alternate_constructor()
4-, das gleiche zu tun, ohne super()
verwenden müßten Sie einen Verweis auf die ungebundene Version von A.alternate_constructor()
erhalten (das heißt die explizite Version der Funktion). Einfach dies 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)
Das oben würde nicht funktionieren, weil die A.alternate_constructor()
Methode eine implizite Referenz nimmt als erstes Argument A
. Die cls
hier weitergegeben werden würde sein zweites Argument sein.
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)
class Child(SomeBaseClass):
def __init__(self):
SomeBaseClass.__init__(self)
Das ist ziemlich einfach zu verstehen.
class Child(SomeBaseClass):
def __init__(self):
super(Child, self).__init__()
Ok, was passiert jetzt, wenn Sie super(Child,self)
verwenden?
Wenn ein Kind Instanz erstellt wird, seine MRO (Methode Resolution Order) in der Größenordnung von (Child, Somebaseclass, Objekt) auf der Vererbung basiert. (Unter der Annahme Somebaseclass hat keine andere Eltern mit Ausnahme des Standardobjekt)
Durch die Übergabe Child, self
, super
sucht im HRG der self
Instanz, und senden Sie das Proxy-Objekt nächsten Kind, in diesem Fall ist es Somebaseclass ist, ruft das Objekt dann die __init__
Methode Somebaseclass. Mit anderen Worten, wenn es super(SomeBaseClass,self)
ist, dass das Proxy-Objekt zurückkehrt super
würde object
Für Multi Vererbung, die MRO viele Klassen enthalten könnte, so dass im Grunde super
können Sie entscheiden, wo Sie in der MRO-Suche gestartet werden soll.
einige großen Antworten hier, aber sie bekämpfen nicht, wie super()
in dem Fall zu verwenden, in denen verschiedenen Klassen in der Hierarchie unterschiedliche Signaturen haben ... vor allem im Fall von __init__
, dass ein Teil zu beantworten und in der Lage sein, effektiv super()
zu verwenden, ich meine Antwort lesen würde vorschlagen, super () und die Änderung der Unterschrift von kooperativen Methoden .
Hier ist nur die Lösung für dieses Szenario:
- die Top-Level-Klassen in der Hierarchie von einer benutzerdefinierten Klasse wie
SuperObject
erben müssen:- , wenn Klassen nehmen Argumente unterscheiden, passieren immer alle Argumente, die Sie auf die Super-Funktion als Schlüsselwort-Argumente erhalten, und, immer
**kwargs
nehmen.
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()
Beispiel für die Verwendung:
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
Viele große Antworten, aber für visuelle Lerner: Zum einen können mit Argumenten zu Super erkunden, und dann ohne.
Stellen Sie sich vor Theres einer Instanz jack
aus der Klasse Jack
erstellt, der die Vererbungskette hat wie in grün in der Abbildung dargestellt. Berufung:
super(Jack, jack).method(...)
wird den MRO (Methode Resolution Order) von jack
(seinen Vererbungsbaum in einer bestimmten Reihenfolge) verwenden, und beginnt von Jack
suchen. Warum kann man eine Elternklasse zur Verfügung stellen? Nun, wenn wir von der Instanz jack
starten Sie die Suche, würde es die Instanzmethode finden, der springende Punkt ist seine Eltern Methode zu finden.
Wenn man nicht Argumente Super liefern, es ist wie das erste übergebene Argument ist die Klasse der self
, und das zweite Argument übergeben ist self
. Diese werden automatisch berechnet für Sie in Python3.
Allerdings sagen wir nicht wollen, Jack
Methode verwenden, anstatt in Jack
von vorbei, wir in Jen
bestanden von könnte nach oben für das Verfahren von Jen
die Suche zu starten.
Es sucht eine Schicht zu einer Zeit (Breite nicht Tiefe), z.B. wenn Adam
und Sue
beide haben die erforderlichen Verfahren, die man von Sue
zuerst gefunden werden.
Wenn Cain
und Sue
beide hatten die gewünschte Methode, Cain
Methode würde zuerst genannt werden.
Dies entspricht im Code:
Class Jen(Cain, Sue):
MRO wird von links nach rechts.