Vererbung von Klassen in Python mit super()?
Hallo Community,
ich habe hier jetzt schon oft gefragt, was diese drei Zeilen bedeuten:
class HangmanGame(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Den gesamten Code gibt es hier zum nachlesen:
https://www.gutefrage.net/frage/wozu-das-super-initkwargs
Jetzt habe ich schon viele Antworten bekommen, vor allem von regex9 und KarlRanseierlll denen ich hier auch noch mal danken möchte.
Aber ich habe es immer noch nicht ganz verstanden und ich bin inzwischen echt verzweifelt. Ich habe aber ein Video dazu gefunden, was ich hier eben verlinke:
https://www.youtube.com/watch?v=QUHnJrFouv8&list=PLzMcBGfZo4-kSJVMyYeOQ8CXJ3z1k7gHn
Konkret geht es um Minute 1:10-3:50. Da sagt er ja, man wisse nicht, wie viele Argumente man bekommt.
Dazu habe ich dann auch direkt meine erste Frage. Ich verstehe nämlich nicht, welche Argumente er meint. Die in der __init__() Methode der Klasse, oder die die man beim erstellen einer Instanz in den Klammer übergeben kann, oder ganz andere? Und wen er die beim erstellen der Instanz meint, frage ich mich, warum nur diese übergeben werden und nicht auch die aus der __init__()-Methode.
Meine zweite Frage wäre dann, ob die beiden Zeilen überhaupt zusammenhängen, oder ob in der Zeile mit dem super() andere kwargs gemeint sind.
Und meine dritte und letzte Frage wäre, warum man diese kwargs-Argumente an die Basisklasse BoxLayout übergeben muss. Bei dieser Frage bitte ich um eine ausführliche aber einfache Antwort. Ich bin nämlich erst 14 und echt nicht der beste in Python. Diese Frage aber bitte nur beantworten wenn Sie sich wirklich damit auskennen oder schon Erfahrung mit Kivy haben.
Ich würde mich unfassbar über jede Antwort freuen. Auch wenn ich weiß, dass ich schon öfter gefragt habe😁. Aber ich muss mich halt erst mal da reinarbeiten.
Viele Grüße
Code Snake🙂
2 Antworten
Ich verstehe nämlich nicht, welche Argumente er meint. Die in der __init__() Methode der Klasse, oder die die man beim erstellen einer Instanz in den Klammer übergeben kann, oder ganz andere?
Erst einmal eine grundsätzliche Begriffsunterscheidung: Es gibt formale Parameter (ugs. oft abgekürzt: Parameter) und es gibt tatsächliche Parameter (meist Argumente genannt).
Bei formalen Parametern handelt es sich um die Parameter, die in den Funktionsköpfen (dazu gehören auch Konstruktoren) aufgelistet werden.
def say_something(text):
print(text)
Die say_something-Funktion hat hier einen formalen Parameter namens text.
Bei Aufruf der Funktion werden ihr Argumente übergeben. Das sind stets konkrete Werte.
greeting = "Hello world!"
say_something(greeting)
Implizit werden diese Argumente / konkreten Werte ihren jeweiligen formalen Parametern zugeordnet. Es wird also eine lokale Variable mit dem Namen des formalen Parameters erstellt und dieser der Wert (auf den in diesem Fall greeting zeigt) zugewiesen.
Bei einem formalen Parameter, der mit ** beginnt, werden alle benannten Argumente in ein Dictionary gespeichert, auf den der formale Parameter verweist.
def describe_person(**info):
if "name" in info:
print("Name:", name)
if "age" in info:
print("Age:", age)
describe_person(name="Max")
describe_person(name="Alison", age="29")
describe_person(hobby="Football", location="Madrid", age="17")
In diesem Beispiel wird beim ersten Aufruf der Funktion nur ein benanntes Argument an die Funktion übergeben. Implizit wird ein Dictionary erstellt, welches den Eintrag name mit dem Wert Max aufnimmt. Der formale Parameter info zeigt auf dieses Dictionary.
Beim zweiten Aufruf werden zwei Einträge in ein Dictionary gelegt: name und age und beim dritten Aufruf drei Einträge: hobby, location und age.
Du siehst an diesem Beispiel, das der Funktion beliebig viele benannte Argumente überreicht werden können. Es ist unbestimmt, welche und wie viele Einträge in das Dictionary gespeichert werden.
Die Funktion kann in ihrem Funktionskörper allerdings entscheiden, wie sie mit den Einträgen aus dem Dictionary umgeht. Im obigen Beispiel werden nur name und age beachtet, sofern es sie gibt.
Bei kivy-Funktionen würdest du Fehlermeldungen zur Laufzeit erhalten, wenn du versuchst, ein benanntes Argument zu übergeben, welches von der Funktion nicht unterstützt wird, denn in kivy wurde eine Validationslogik dafür eingebaut.
Beispiel:
layout = BoxLayout(background_color=(1, 0, 1, 1)) # error, background_color is not supported
Meine zweite Frage wäre dann, ob die beiden Zeilen überhaupt zusammenhängen, oder ob in der Zeile mit dem super() andere kwargs gemeint sind.
Es ist immer noch die gleiche Variable mit dem gleichen Inhalt. Bei Aufruf von __init__ wird sie (kwargs) erstellt und anschließend als Argument an die __init__-Funktion der Basisklasse weitergereicht.
Das ist vergleichbar mit diesem Fall:
def say_name(name):
print(name)
def say_welcome(name):
print("Welcome")
say_name(name)
say_welcome("Julia")
Der Wert Julia wird in say_welcome in der Variable name abgelegt und über diese dann an say_name weitergereicht.
Aber womöglich sollte zu der Bedeutung der beiden Sternchen noch etwas ergänzt werden, denn sie haben an dieser Stelle noch eine spezielle, wichtige Funktion.
Der Parameter kwargs zeigt (wie schon geschrieben) auf ein Dictionary, in welches alle benannten Argumente hineingespeichert werden, sobald die Funktion aufgerufen/ausgeführt wird.
def __init__(self, **kwargs):
# kwargs is a dict now
super().__init__(**kwargs) # call of base constructor
Die __init__-Funktion der Basisklasse erwartet allerdings eine Liste an benannten Argumenten, so wie es auch schon die __init__-Funktion der eigenen Klasse tut. Stände in kwargs bspw.
{ "a" = 1, "b" = 2 }
müsste der Aufruf des Basiskonstruktors folgendermaßen aussehen:
super().__init__(a=1, b=2)
Die Sternchen helfen dabei nun aus, das Dictionary kwargs in diese erwartete Form zu bringen. Sie konvertieren/entpacken das Dictionary in eine Liste benannter Parameter.
Abschließend dazu noch einmal ein einfaches Beispiel zum Vergleich:
def say_word_pairs(**word_pairs):
for key, value in word_pairs.items():
print("{0} = {1}".format(key, value))
word_pairs = { "tree": "apple", "sky": "cloud" }
say_word_pairs(word_pairs) # error
say_word_pairs(**word_pairs) # correct
say_word_pairs(tree="apple", sky="cloud") # correct
Die Funktion erwartet für einen Aufruf immer benannte Argumente. Ein reines Dictionary kann nicht übergeben werden, sondern muss erst mittels ** konvertiert werden.
Und meine dritte und letzte Frage wäre, warum man diese kwargs-Argumente an die Basisklasse BoxLayout übergeben muss.
Noch einmal, so wie in einer deiner vorherigen Fragen, werde ich nicht darauf eingehen. Teste es doch einfach an einer Beispielanwendung:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.widget import Widget
class MyApp(App):
def build(self):
return MyBoxLayout(orientation="vertical", spacing=20)
class MyBoxLayout(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs) # pass kwargs or not
self.btn1 = Button(text="One")
self.btn2 = Button(text="Two")
self.add_widget(self.btn1)
self.add_widget(self.btn2)
if __name__ == "__main__":
MyApp().run()
Prüfe, wie die Anwendung sich verhält, wenn **kwargs an den Basiskonstruktor übergeben wird und wie es ausschaut, wenn sie es nicht tut.
Ich habe das auch schon als Frage gestellt, aber damit Sie es auf jeden Fall sehen, (ich schätze Ihre Antworten sehr und da Sie einer der einzigen mit Kivyerfahrung hier seien dürfen können auch nur Sie mir wirklich helfen)schreibe ich es auch noch mal hier als Kommentar:
Warum braucht man super() nur bei __init__()-Methoden?
Also eigentlich sagt der Titel schon alles. In einem Tutorial hat jemand nämlich gesagt, wenn man die __init__() Methode überschreibt sollte man immer super() aufrufen. Aber wieso bei anderen Methoden nicht?
LG Code Snake
Da sagt er ja, man wisse nicht, wie viele Argumente man bekommt.
Es ist undefiniert, wie viele benannte Argumente bei Instanzierung der Klasse an den Konstruktor übergeben werden.
(...) oder ob in der Zeile mit dem super() andere kwargs gemeint sind.
Nein. Der Parameter kwargs wird als Argument an den Basiskonstruktor weitergereicht.
Das ist auch das Einzige, was explizit an den Basiskonstruktor übergeben wird.
Denn z.B. im Video schreibt der Creator ja self.cols=2 in den Konstruktor.
Die Klasse MyGrid erbt alle Eigenschaften und Methoden vom GridLayout. Daher kann in ihren Methoden/Konstruktoren auch auf diese zugegriffen und deren Zustand geändert werden.
Mit dem nächstliegenden Zeichenvorgang (der durch das Anhängen der Widgets getriggert wird) kann dieser Zustand (zwei Spalten) berücksichtigt werden.
Die Klasse MyGrid erbt alle Eigenschaften und Methoden vom GridLayout. Daher kann in ihren Methoden/Konstruktoren auch auf diese zugegriffen und deren Zustand geändert werden.
Ok. Also muss man die Instanzvariablen wie self.cols=2 aus dem Konstruktor von MyGrid nicht an die Basisklasse übergeben, da die Basisklasse schon darauf zugreifen kann. Aber die Argumente, die man beim erstellen einer Instanz übergibt: MyApp(cols=2, rows=3) muss man mit dem **kwargs an die Basisklasse übergeben, da sie sonst keinen Zugriff darauf hat?
Also ChatGPT hat mir auf die Frage in meinem Kommentar unmittelbar über diesem so geantwortet:
Genau, du hast es richtig verstanden.
Die Instanzvariablen wie
self.cols=2
im Konstruktor der Klasse
MyGrid
müssen nicht explizit an die Basisklasse übergeben werden, da die Basisklasse
GridLayout
bereits auf diese Eigenschaften zugreifen kann. Da
MyGrid
von
GridLayout
erbt, erbt es automatisch alle Eigenschaften und Methoden der Basisklasse.
Allerdings müssen die Argumente, die beim Erstellen einer Instanz von
MyGrid
übergeben werden, wie
MyApp(cols=2, rows=3)
, an die Basisklasse mit
super().__init__(**kwargs)
übergeben werden. Dadurch werden diese Argumente im
**kwargs
-Dictionary gespeichert und von der Basisklasse verarbeitet. Dies ist erforderlich, da die Basisklasse
GridLayout
möglicherweise ihre eigenen Parameter erwartet oder über benannte Argumente gesteuert wird. Durch die Weitergabe des
**kwargs
an die Basisklasse kann die Basisklasse auf diese Argumente zugreifen und entsprechend handeln.
Zusammenfassend: Instanzvariablen müssen nicht explizit an die Basisklasse übergeben werden, da diese bereits darauf zugreifen kann. Argumente, die beim Erstellen einer Instanz übergeben werden, müssen jedoch über
super().__init__(**kwargs)
an die Basisklasse übergeben werden, damit diese darauf zugreifen und entsprechend verarbeiten kann.
Jetzt wollte ich Sie fragen, ob das so richtig ist. Damit wären meine Fragen dann nämlich final geklärt.
Ok. Vielen vielen Dank für dieses sehr ausführliche und gute Antwort. Könntest du vielleicht nur noch mal als Übersicht auf die ersten beiden Fragen mit einem Satz bzw. Ja oder Nein antworten könntest. Dann bekommst du den Stern für die beste Antwort zu 100%.
Also z.B. konkret bei der build()-Methode. Warum braucht man das super() bei der __init__()-Methode aber nicht bei der build()-Methode?
Noch eine letzte Frage. Ich weiß dass ich Sie wahrscheinlich etwas nerve. Aber Kivy kennen halt nur wenige, weshalb ich schwer andere fragen kann.
Also und zwar frage ich mich, ob man auch einfach alle Instanzvariablen die in beispielsweise der __init__()-Methode der Subclass von GridLayout stehen beim erstellen der Instanz übergeben könnte, denn dann würde ja alles an die Basisklasse weitergegeben werden es müsste keinen geben, der die Argumente verarbeitet/speichert, da die Basisklasse dies schon übernehmen könnte.
Vlt können dir die Videos von dem :https://youtube.com/@ArjanCodes helfen. Schau dir mal die Code roasts an, dass hilft vlt.
Also ich habe verstanden, was Sie erklärt haben mit den Argumenten und Parametern. Aber ich konnte die Antwort auf meine Frage nicht wirklich herauslesen
Vielleicht können Sie ja nochmal beantworten, ob er die Instanzvariablen in der __init__() Methode an die Basisklasse übergibt, oder die Argumente beim erstellen einer Instanz. Und wenn die in der __init__() Methode nicht übergeben werden, frage ich mich wieso das der Fall ist. Denn z.B. im Video schreibt der Creator ja self.cols=2 in den Konstruktor. Aber wenn diese Variable nicht übergeben wird, bringt das ja nichts.