Seite 1 von 2

Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 12:59
von egon11
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?

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 13:14
von __deets__
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.

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 13:35
von kbr
@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.

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 14:25
von egon11
Also sollte man immer super() verwenden?

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 14:38
von __deets__
Ja.

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 14:41
von egon11
OK alles klar.
vielen dank

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 16:55
von __blackjack__
Tja, ich würde sagen nein. Man sollte nie `super()` verwenden. https://fuhm.net/super-harmful/

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 17:49
von kbr
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.

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 18:23
von narpfel
@__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.

Re: Schlüsselwort super()

Verfasst: Samstag 6. März 2021, 18:34
von kbr
@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.

Re: Schlüsselwort super()

Verfasst: Sonntag 7. März 2021, 00:04
von __blackjack__
@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.

Re: Schlüsselwort super()

Verfasst: Sonntag 7. März 2021, 00:17
von __blackjack__
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‽

Re: Schlüsselwort super()

Verfasst: Sonntag 7. März 2021, 05:25
von snafu
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. 🤷‍♂️

Re: Schlüsselwort super()

Verfasst: Sonntag 7. März 2021, 12:07
von __blackjack__
@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.

Re: Schlüsselwort super()

Verfasst: Montag 8. März 2021, 06:35
von snafu
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.

Re: Schlüsselwort super()

Verfasst: Montag 8. März 2021, 09:06
von snafu
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.

Re: Schlüsselwort super()

Verfasst: Montag 8. März 2021, 12:06
von kbr
@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.

Re: Schlüsselwort super()

Verfasst: Montag 8. März 2021, 22:23
von snafu
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

Re: Schlüsselwort super()

Verfasst: Dienstag 9. März 2021, 10:18
von kbr
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.

Re: Schlüsselwort super()

Verfasst: Dienstag 9. März 2021, 22:06
von snafu
@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.