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.
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.
SomeBaseClass.__init__(self)
bedeutet, SomeBaseClass
s __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.
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.
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.
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.
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
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
super
sollte keinen Zugriff aufUnsuperChild
haben. Warum ist die Schlussfolgerung "Vermeiden Sie immerInjectMe
" 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.
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.
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)
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:
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 verwendenz.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)
super
mit einer Instanzmethodez.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 verwendenclass 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 A
s 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)
Viele tolle Antworten, aber für visuelle Lernende: Erst mit Argumenten nach Super suchen lassen, dann ohne.
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.
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.
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:
- die Klassen der obersten Ebene in Ihrer Hierarchie müssen von einer benutzerdefinierten Klasse wie
SuperObject
erben:- 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