Vererbung von Klassen in Python mit super()?

2 Antworten

Vom Beitragsersteller als hilfreich ausgezeichnet
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.


CodeSnake 
Beitragsersteller
 18.05.2023, 20:19

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%.

0
CodeSnake 
Beitragsersteller
 23.05.2023, 19:20
@CodeSnake

Also z.B. konkret bei der build()-Methode. Warum braucht man das super() bei der __init__()-Methode aber nicht bei der build()-Methode?

0
CodeSnake 
Beitragsersteller
 23.05.2023, 20:07
@CodeSnake

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.

0
CodeSnake 
Beitragsersteller
 18.05.2023, 20:45

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

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.

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.

0
regex9  19.05.2023, 05:35
@CodeSnake
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.

1
CodeSnake 
Beitragsersteller
 19.05.2023, 08:54
@regex9
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?

0
CodeSnake 
Beitragsersteller
 19.05.2023, 09:06
@CodeSnake

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.

0
CodeSnake 
Beitragsersteller
 19.05.2023, 15:10
@regex9

Also kann die Basisklasse nur die Instanzvariablen standardmäßig durch die Vererbung verarbeiten und die hier

MyApp(cols=2, rows=3)

muss man über **kwargs an die Basisklasse explizit übergeben? (Wenn möglich bitte eine Ja/Nein Antwort)

0
CodeSnake 
Beitragsersteller
 23.05.2023, 16:19

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

0

Vlt können dir die Videos von dem :https://youtube.com/@ArjanCodes helfen. Schau dir mal die Code roasts an, dass hilft vlt.

Woher ich das weiß:Studium / Ausbildung – Information Engineering Studium