Schlüsselwort super()

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
egon11
User
Beiträge: 354
Registriert: Mittwoch 14. Dezember 2016, 20:59

Hallo, auch wenn es vielleicht schon x-mal irgendwo steht, weiss ich immer noch nicht zu 100% was der Unterschied zwischen

Code: Alles auswählen

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

class B(A):
    def __init__(self):
        super().__init__()
und

Code: Alles auswählen

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

class B(A):
    def __init__(self):
        A.__init__(self)
ist.
Was wird wann und wo eingesetzt und was ist der Unterschied?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

In deinem Beispiel keiner. Bei einer einfachen, linearen Ableitungshierarchie gibt es keinen.

Aber fuer sowas hier:

Code: Alles auswählen

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


class B(A):   # Kann eingefuehrt werden oder entfernt
    def __init__(self):
        super().__init__()
        print("B")


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

C()
super findet *alle* Vorgaenger, aber genau einmal, und bringt sie in die richtige MRO (method resolution order).

Dein explizites aufrufen von __init__ wuerde hier nicht funktionieren, sollte sich B ohne dein zutun aendern, und ploetzlich NICHT mehr von A ableiten.

Es ist kein besonders haeufiger Fall, aber fuer diesen Fall ist es gemacht.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@egon11: Wäre vielleicht noch hinzuzufügen, dass Du beide Varianten nicht mischen solltest. Also, wenn "super()" genutzt wird, dann auch durchgehend verwenden und auch in der Basis-Klasse einsetzen. Wenn es zur Mehrfachvererbung kommt, wird letzteres wichtig zur korrekten MRO-Auflösung. Aber vermeide Mehrfachvererbung wann immer Du kannst. Es ist nicht trivial, eine Programmstruktur mit Mehrfachvererbung gut aufzusetzen.
egon11
User
Beiträge: 354
Registriert: Mittwoch 14. Dezember 2016, 20:59

Also sollte man immer super() verwenden?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja.
egon11
User
Beiträge: 354
Registriert: Mittwoch 14. Dezember 2016, 20:59

OK alles klar.
vielen dank
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Tja, ich würde sagen nein. Man sollte nie `super()` verwenden. https://fuhm.net/super-harmful/
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Auf diesen Link habe ich gewartet :)

Der ursprüngliche Titel "Python's Super Considered Harmful" wurde vom Autor abgeändert in "Python's Super is nifty, but you can't use it", nachdem dieser Beitrag hier erschien:
https://rhettinger.wordpress.com/2011/0 ... ed-super/
Ich bin der Ansicht, Python sollte die MRO bauen. Es hilft natürlich zu verstehen, was dabei passiert; ggf. inklusive der C3 Linearisierung.
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@__blackjack__: Ich würde ein anderes Fazit aus dem Artikel ziehen: Wenn man keine Mehrfachvererbung benutzt, dann tritt keins der genannten Probleme auf und `super()` verweist immer auf die Elternklasse. Und wenn man Mehrfachvererbung benutzt, dann muss man sowohl mit als auch ohne `super()` aufpassen, dass man die Methoden der Elternklassen korrekt aufruft und keine Methoden auslässt oder mehrfach aufruft.

Von daher sehe nicht, was gegen `super()` spricht, wenn man keine Mehrfachvererbung benutzt. Es gibt aber einen (für mich) sinnvollen Grund, `super()` zu benutzen: Wenn man die Elternklasse ändert/umbenennt, kann man es nicht vergessen, den Namen in allen Methoden auch zu ändern.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@narpfel: seit Python 3 gilt das auch für Kind-Klasse – der Startpunkt zur MRO-Auflösung braucht nicht mehr explizit angegeben zu werden. Allein das war aus meiner Sicht bereits ein guter Grund zu Python 3 zu wechseln.
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@narpfel: Es ist halt unnötige Arbeit weil ja doch irgendwo Mehrfachvererbung verwendet werden kann und deshalb reicht es nicht einfach `super()` zu verwenden, sondern man muss überall nocht *args, und **kwargs entgegennehmen und weitergeben und sollte nur Schlüsselwortargumente verwenden. Wenn Du einfach nur `super()` verwendest ohne die anderen Sachen auch umzusetzen, dann schreibst Du halt wissentlich kaputten Code der nur solange funktioniert wie keiner auf die Idee kommt Mehrfachvererbung zu verwenden. Dann funktioniert er nur noch wenn alle alle Methoden die gleiche Signatur haben.

Schob beim Titel von dem Hettinger-Artikel habe ich mich gefragt ob der mich verarschen will. `super()` ist kompliziert und ich denke die allermeisten wissen gar nicht was sie da machen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Um mal auf das Beispiel aus dem ersten Beitrag zurück zu kommen: Das ist kaputt! Wenn man `super()` benutzt, dann muss man das überall benutzen. Auch in der Basisklasse!

Und falls man nicht ausschliessen kann, das irgendwann einmal eine Kindklasse die Signatur ändert, also beispielsweise mal ein Argument erwartet, und das will man nicht ausschliessen, dann muss man auch überall noch beliebige Argumente erwarten und weiterreichen:

Code: Alles auswählen

class A:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print("hello")

class B(A):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
Und das muss man bei jedem verf…luchten Aufruf machen in dem `super()` vorkommt. Womit Python einem dann aber nicht mehr sagen kann, das man sich bei einem Schlüsselwortargument vertippt hat, weil so diese Methoden ja einfach jedes Schlüsselwortargument akzeptieren, auch welche die es gar nicht ”gibt”. Die werden dann einfach ignoriert. Wie man `super()` Super finden kann ist mir ein Rätsel. Also nicht average Joe der's nicht verstanden hat. Aber Hettinger‽
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das mit *args und **kwargs halte ich für konstruiert. Warum sollte man super() nicht ganz normal mit den Argumenten aufrufen, die man für die Basisklasse erwartet? Mit super() hat man ja bloß einen Alias. Niemand zwingt einen, dass man jede erdenkliche Signatur damit imitiert. 🤷‍♂️
Benutzeravatar
__blackjack__
User
Beiträge: 13080
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@snafu: Weil es ja nicht die Basisklasse gibt, es gibt mit `super()` die nächste Klasse/Methode in der MRO und das muss nicht die Basisklasse sein. Und was diese Methode (zusätzlich) erwartet weisst Du nicht, denn die kennst Du ja im Zweifelsfall gar nicht. Und umgekehrt weisst Du halt auch nicht wer Deine Methode mit welchen Argumenten aufruft. Also muss man das immer mit weiterreichen. Egal ob man `super()` nun mag oder nicht, das sagen *beide* Artikel das man das machen muss und auch warum man das machen muss.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Trifft aber, wie schon geschrieben wurde, nur bei Mehrfachvererbung zu. Und selbst dafür ist die Reihenfolge der Klassen in der MRO definiert, wenn auch schwerer durchschaubar. Dass man immer *args, **kwargs schreiben muss, weil es ja evtl eine Mehrfachvererbung in einer der Elternklassen geben könnte, halte ich für etwas weit hergeholt als generelles Argument gegen super(). Aber das soll jedeR selbst entscheiden.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wobei ich nun das Problem sehe: Angenommen man hat die Klasse Spam, die von X ableitet und benutzt darin super(), um eine Methode der Klasse X aufzurufen. Nehmen wir weiter an, dass eine Klasse Eggs von der Spam ableitet und zudem von einem Mixin (also Mehrfachvererbung nutzt). Dann kann es passieren, dass sich das Mixin in der MRO quasi zwischen Spam und X geschoben hat. Spam ruft dann mittels super() plötzlich nicht mehr X auf, sondern das Mixin und das ist natürlich verwirrend. Mit anderen Worten: Obwohl sich nichts am Code von Spam geändert hat, wurde nur durch Mehrfachvererbung in der *abgeleiteten* Klasse das Verhalten der Elternklasse beeinflusst. Und das war mir bisher tatsächlich nicht bewusst.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@snafu: was Du mit Deinem Beispiel beschreibst, passiert dann, wenn Du

Code: Alles auswählen

class Eggs(Spam, Mixin): ...
statt

Code: Alles auswählen

class Eggs(Mixin, Spam): ...
definierst. Dies ist eine der Tücken der Linearisierung bei Mehrfachvererbung.

Mixins sollten bei Mehrfachvererbung immer zuerst angegeben werden und die Klasse, von der eigentlich abgeleitet wird, zuletzt. Idealerweise werden bei Mehrfachvererbung nur Mixins genutzt, die zusätzliche Methoden bereitstellen, oder, falls eine Überladung passiert, die gleiche oder eine erweiterte Signatur bieten. Mixins sollten auch keine __init__ Methode haben. Dann wird die *args/**kwargs Situation einfacher. Mehrfachvererbung ohne Mixins ist schwieriger zu entwerfen.

Ohne Mehrfachvererbung ist super() (scheinbar) leicht zu verstehen, praktisch und funktioniert auch dann richtig, wenn es falsch angewandt wird (falsch in dem Sinne, dass es auch bei Mehrfachvererbung korrekt funktionieren soll).

Mit Mehrfachvererbung dagegen schaut es anders aus: hier muss die MRO verstanden werden und die Konsequenzen daraus.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wobei es irgendwie doch wie erwartet funktioniert:

Code: Alles auswählen

In [5]: %paste
class X:
    def foo(self):
        print("Method foo() of X was called")

class Spam(X):
    def foo(self):
        print("Method foo() of Spam was called")
        super().foo()

class Mixin:
    pass

class Eggs(Spam, Mixin):
    def __init__(self):
        print("MRO for Eggs:", type(self).__mro__)

class Ham(Mixin, Spam):
    def __init__(self):
        print("MRO for Ham:", type(self).__mro__)

def main():
    Eggs().foo()
    Ham().foo()

if __name__ == "__main__":
    main()

## -- End pasted text --
MRO for Eggs: (<class '__main__.Eggs'>, <class '__main__.Spam'>, <class '__main__.X'>, <class '__main__.Mixin'>, <class 'object'>)
Method foo() of Spam was called
Method foo() of X was called
MRO for Ham: (<class '__main__.Ham'>, <class '__main__.Mixin'>, <class '__main__.Spam'>, <class '__main__.X'>, <class 'object'>)
Method foo() of Spam was called
Method foo() of X was called
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Die Unterschiede zeigen sie sich in Deinem Beispiel nicht, da das Mixin nichts tut. Implementiere einmal in allen Klassen __init__() und foo() und spiele ein wenig herum. Dann werden die Unterschiede deutlich.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@kbr: Klar, dann wird unter den von uns beiden genannten Umständen die "falsche" foo()-Methode aufgerufen. Aber wie oft kommt sowas vor, dass man gleichzeitig von zwei Klassen erbt, die eine oder mehrere gleichlautende Methoden besitzen und wo man genau diese Methode anspricht? Damit kann man sich natürlich in den Fuß schießen, nur wird jemand beim (unbedachten) Nutzen von super() wohl nur sehr selten auf diese Konstellation treffen. Das Argument ist IMHO jedenfalls nicht stark genug, um auf super() zu verzichten. Zumindest in Python 3 ist es doch ganz praktisch geworden.
Antworten