12. Unterrichtsblock

Kursinhalte

Python Vererbung

Durch Vererbung kann eine Klasse definiert werden, die alle Methoden und Eigenschaften einer anderen Klasse erbt. Die übergeordnete Klasse ist die Klasse, von der geerbt wird, auch Basisklasse genannt.

Eine untergeordnete Klasse ist die Klasse, die von einer anderen Klasse erbt, auch abgeleitete Klasse genannt.

Die erbende Klasse übernimmt alle Eigenschaften und Methoden der beerbten Klasse!

Verwendungsmöglichkeiten der Vererbung

  1. Da eine untergeordnete Klasse alle Funktionalitäten der übergeordneten Klasse erben kann, ermöglicht dies die Wiederverwendbarkeit von Code.
  2. Sobald eine Funktionalität entwickelt ist, kann man diese einfach erben. Das Rad muss nicht neu erfunden werden. Dies ermöglicht einen saubereren Code und eine einfachere Wartung.
  3. Da man in der untergeordneten Klasse auch eigene Funktionalitäten hinzufügen kann, können diese nur die nützlichen Funktionalitäten erben und andere erforderliche Funktionen definieren.

Syntax

Hier ist die Syntax der Vererbung in Python:

# Definition einer Elternklasse
class eltern_klasse:
    # Attribute und Methoden definieren

# Vererbung
class kind_klasse(eltern_klasse):
    # Attribute und Methoden der eltern_klasse
    # Attribute und Methoden der kind_klasse

Hier erben wir die Klasse kind_klasse aus der Klasse eltern_klasse.

Beispiel einer Vererbung von Attributen und Methoden

class tier:

    # Attribute und Methoden der Elternklasse
    name = ""
    fellfarbe = "schwarz"
    
    def futter(self):
        print("Ich liebe Knochen")

# Vererbung von Elternklasse "tier" an neue Kindklasse "hund"
class hund(tier):

    # Neue Methode in Kindklasse
    def anzeige(self):
        # Zugriff auf Namen-Attribut der Kindklasse mit dem "self" Keyword
        print("Mein Name ist", self.name, "und mein Fell ist", self.fellfarbe)

# Erstellung eines Objekts der Kindklasse
labrador = hund()

# Zugriff auf Elternklassen-Attribut und Zuteilung eines Attributwerts
labrador.name = "Bello"

# Aufruf der Methode aus der Kindklasse
labrador.anzeige()

# Aufruf der Methode aus der Elternklasse
labrador.futter()

Im obigen Beispiel haben wir eine Kindklasse hund von einer Elternklasse tier abgeleitet.

Hier verwenden wir labrador (Objekt von hund ), um auf den Namen und die Methode futter() der tier Elternklasse zuzugreifen. Dies ist möglich, weil die Kindklasse alle Attribute und Methoden der Elternklasse erbt.

Außerdem wurde mithilfe von self auf das name-Attribut innerhalb einer Methode der hundKlasse zugegriffen.

Schauen wir uns ein weiteres Beispiel für die Vererbung in Python an:

Ein Polygon ist eine geschlossene Figur mit mindestens drei oder mehr Seiten. Folgendes Beispiel zeigt eine Klasse namens Polygon wie folgt definiert:

class Polygon:
    def __init__(self, seitenzahl):
        self.n = seitenzahl
        self.seiten = [0 for i in range(seitenzahl)]

    def eingabeSeiten(self):
        self.seiten = [float(input("Gibt die Länge der "+str(i+1)+". Seite ein: ")) for i in range(self.n)]

    def anzeigeSeiten(self):
        for i in range(self.n):
            print("Seite",i+1,"is",self.seiten[i])

Diese Klasse verfügt über Datenattribute zum Speichern der Anzahl der Seiten n und der Größe jeder Seite als Liste bezeichnet seiten.

  • Die eingabeSeiten()Methode berücksichtigt die Größe jeder Seite
  • Die anzeigeSeiten()Methode zeigt diese Seitenlängen an

Ein Dreieck ist ein Polygon mit 3 Seiten. Wir können nun also eine Klasse namens Dreieck erstellen, die von Polygon erbt. Dies macht alle Attribute der Polygon-Klasse für die Dreieck-Klasse verfügbar.

Wir müssen sie also nicht erneut definieren (Wiederverwendbarkeit des Codes). Unsere Dreieck kann wie folgt definiert werden:

class Dreieck(Polygon):
    def __init__(self):
        Polygon.__init__(self,3)

    def ermittleFlaeche(self):
        a, b, c = self.seiten
        # Semiperimeter (Halbmesser) berechnen
        s = (a + b + c) / 2
        # Flächenberechnung mit der Heron-Formel
        flaeche = (s*(s-a)*(s-b)*(s-c)) ** 0.5
        print('Die Fläche des Dreiecks ist ', flaeche, 'cm²')

Die Klasse Dreieck erbt alle Attribute und Methoden der Elternklasse Polygon. Allerdings hat die Dreieck-Klasse die neue Methode ermittleFlaeche()um die Fläche des Dreiecks zu ermitteln und auszugeben.

Sehen wir uns nun den vollständigen Arbeitscode des obigen Beispiels an, einschließlich der Erstellung eines Objekts.

# Elternklasse Polygon
class Polygon:
    # Initialisierung der Seitenzahl
    def __init__(self, seitenzahl):
        self.n = seitenzahl
        self.seiten = [0 for i in range(seitenzahl)]

    def eingabeSeiten(self):
        self.seiten = [float(input("Gibt die Länge der "+str(i+1)+". Seite ein: ")) for i in range(self.n)]
    
    # Methode zur Anzeige einer jeder Seitenlänge des Polygons 
    def anzeigeSeiten(self):
        for i in range(self.n):
            print("Seite",i+1,"is",self.seiten[i])


# Kindklasse Dreieck
class Dreieck(Polygon):
    # Initialisierung der Seitenzahl des Dreiecks auf 3 durch 
    # Aufruf der __init__ Methode aus der Polygon Klasse
    def __init__(self):
        Polygon.__init__(self,3)

    def ermittleFlaeche(self):
        a, b, c = self.seiten

        # Semiperimeter (Halbmesser) berechnen
        s = (a + b + c) / 2

        # Flächenberechnung mit der Heron-Formel
        flaeche = (s*(s-a)*(s-b)*(s-c)) ** 0.5
        print('Die Fläche des Dreiecks ist ', flaeche, 'cm²')



# Erstellung eines Objekts der Dreieck-Klasse
d = Dreieck()

# Anweisung des Nutzers zur Eingabe der Seitenlängen des Dreiecks
d.eingabeSeiten()

# Anzeige der Seiten des Dreiecks
d.anzeigeSeiten()

# Berechnung und Ausgabe der Fläche des Dreiecks
d.ermittleFlaeche()

Hier kann man sehen, dass obwohl in der Klasse Dreieck keine Methoden wie eingabeSeiten() oder anzeigeSeiten() definiert wurde, diese für die Aufgabe in der Klasse Dreieck dennoch genutzt werden konnten.

Wenn ein Attribut oder eine Methode in der Klasse selbst nicht gefunden wird, wird die Suche in der Elternklasse fortgesetzt. Dies wiederholt sich rekursiv, wenn die Elternklasse selbst von anderen Klassen abgeleitet ist.

Aufgabe

Finde eine Methode, um bei der Flächenberechnung im Ergebnis nur zwei Ziffern hinter dem Komma ausgeben zu lassen und implementiere die Lösung in o.g. Aufbau.

Erstelle eine Kindklasse „Quadrat“ und eine Kindklasse „Hexagon“ und erstelle innerhalb der Kindklassen Methoden zur Flächenberechnung.

Rufe die Methoden über ein Objekt der Kindklassen auf.

Methodenüberschreibung in der Python-Vererbung

Im vorherigen Beispiel ist zu sehen, dass das Objekt der Kindklasse auf die Methode der Elternklasse zugreifen kann.

Was aber, wenn dieselbe Methode sowohl in der Elternklasse als auch in der Kindklasse vorhanden ist?

In diesem Fall überschreibt die Methode in der Kindklasse die Methode in der Elternklasse. Dieses Konzept wird in Python als Methodenüberschreibung bezeichnet und ähnelt dem Kaskadensystem in CSS.

class tier:

    # Attribute und Methoden der Elternklasse
    name = ""
    fellfarbe = "schwarz"
    
    def futter(self):
        print("Ich liebe Knochen")

# Vererbung von Elternklasse "tier" an neue Kindklasse "hund"
class hund(tier):

    # Überschreiben der futter() Methode
    def futter(self):
        print("Ich liebe Hausschuhe")


# Erstellung eines Objekts der Kindklasse
labrador = hund()

# Aufruf der Methode aus der Kindklasse (Nicht mehr aus der Elternklasse)
labrador.futter()

Aufgabe

Schreibe o.g. Aufbau so um, dass über ein Objekt auch noch die futter()-Methode der Klasse tier aufgerufen wird.

Die super()-Methode in der Python-Vererbung

Zuvor haben wir gesehen, dass dieselbe Methode in der Unterklasse die Methode in der Oberklasse überschreibt.

Wenn man jedoch von der Unterklasse aus auf die Methode der Oberklasse zugreifen muss, verwendet man die super()-Methode. Zum Beispiel:

class tier:

    # Attribute und Methoden der Elternklasse
    name = ""
    fellfarbe = "schwarz"
    
    def futter(self):
        print("Ich liebe Knochen")

# Vererbung von Elternklasse "tier" an neue Kindklasse "hund"
class hund(tier):

    # Überschreiben der futter() Methode
    def futter(self):

        # Ruft zusätzlich die futter()-Methode der Elternklasse auf
        super().futter()
        
        print("Ich liebe Hausschuhe")

        


# Erstellung eines Objekts der Kindklasse
labrador = hund()

# Aufruf der Methode aus der Kindklasse & Elternklasse 
labrador.futter()

Aufgabe

Die Klasse hund soll neben der Elternklasse tier auch noch die Eigeschaften einer weiteren Elternklasse familienmitglied erben. Baue eine Klasse familienmitglied mit eigenen Attributen und Methoden und rufe diese über das Objekt labrador auf.

Zusammenfassung

Eine Python-Klasse ist ein Bauplan oder eine Vorlage zur Erzeugung von Objekten, die Attribute (Eigenschaften) und Methoden (Funktionen) definieren, die die Objekte gemeinsam nutzen. Sie ist ein zentrales Konzept der objektorientierten Programmierung (OOP) und ermöglicht die logische Gruppierung von Code und die Wiederverwendung. 

Aufbau einer Klasse

  • Definition: Eine Klasse wird mit dem Schlüsselwort class und einem Klassennamen, der normalerweise mit einem Großbuchstaben beginnt, definiert. 
  • Syntax: class KlassenName:
  • Klassenkörper: Alle Anweisungen innerhalb der Klasse müssen eingerückt sein. Wenn die Klasse vorläufig leer bleibt, kann das Schlüsselwort pass verwendet werden, um einen Syntaxfehler zu vermeiden. 

Attribute und Methoden

  • Attribute: Dies sind die Daten, die Objekte der Klasse besitzen. Sie werden oft innerhalb der __init__-Methode zugewiesen.
    • self.attribut_name = wert 
  • Methoden: Dies sind Funktionen, die innerhalb einer Klasse definiert sind und das Verhalten der Objekte festlegen.
    • def methoden_name(self):
    • self bezieht sich immer auf die spezifische Instanz der Klasse. 

Objekte (Instanzen)

  • Erzeugung: Nach der Definition der Klasse können Objekte (Instanzen) davon erstellt werden.
    • mein_objekt = KlassenName(argumente)
  • Unterschied: Eine Klasse ist der Bauplan, während ein Objekt eine konkrete Ausprägung dieses Bauplans ist. Jedes Objekt hat seine eigenen Werte für die Attribute. 

Beispiele

  • Klasse Auto:
    • Attribute: farbemarke
    • Methoden: fahren()bremsen()
  • Instanz mein_auto:
    • mein_auto = Auto(farbe="rot", marke="Toyota") 

Verwendung

  • Klassen können wiederverwendet und erweitert werden: (Vererbung), um Code-Redundanz zu vermeiden. 
  • Klassen helfen dabei, Code logisch zu strukturieren: und Funktionen, die zusammengehören, zu gruppieren. 
  • Klassen können in anderen Modulen definiert werden: und dort dann importiert werden. 

JSON

JSON steht für JavaScript Object Notation (JavaScript-Objektnotation).

Im Kern ist JSON ein unabhängiges, textbasiertes Datenformat zur Speicherung und Übertragung von strukturierten Daten. Es wurde ursprünglich aus der Programmiersprache JavaScript abgeleitet, hat sich aber aufgrund seiner einfachen, menschlich lesbaren Struktur zum Standardformat für den Datenaustausch zwischen Web-Servern (wie z. B. einem Flask-Backend) und Clients (Webbrowsern, mobilen Apps) entwickelt.

JSON-Daten werden als Schlüssel-Wert-Paare organisiert, was sie direkt vergleichbar mit Python-Dictionaries (Wörterbüchern) macht.

Wichtigste Merkmale

  • Format: Es ist ein reiner Text-String. Das bedeutet, es kann problemlos über Netzwerke gesendet werden (z. B. in einer E-Mail oder über eine HTTP-Anfrage).
  • Struktur: Daten werden in geschweiften Klammern {} (Objekte, vergleichbar mit Python-Dictionaries) und eckigen Klammern [] (Arrays, vergleichbar mit Python-Listen) organisiert.

JSON und Python: Das json-Paket

Python verfügt über eine hervorragende, native Unterstützung für JSON. Sie müssen keine zusätzlichen Bibliotheken installieren, da ein Modul namens json bereits in der Standardbibliothek enthalten ist.

Dieses Modul stellt die notwendigen Funktionen bereit, um Daten zwischen dem JSON-Format (dem Text-String) und den nativen Python-Datenstrukturen (Dictionaries, Listen usw.) zu konvertieren.

Die zwei wichtigsten Funktionen sind:

  1. json.loads() (Load String):
    • Zweck: Nimmt einen JSON-String (Text) als Eingabe und konvertiert ihn in ein Python-Objekt (typischerweise ein Dictionary).
    • Anwendung: Wird verwendet, wenn Ihr Flask-Server Daten von einem Client empfängt.
  2. json.dumps() (Dump String):
    • Zweck: Nimmt ein Python-Objekt (typischerweise ein Dictionary oder eine Liste) und konvertiert es in einen JSON-String (Text).
    • Anwendung: Wird verwendet, wenn Ihr Flask-Server Daten als Antwort an den Client sendet.

Merksatz: JSON ist die Sprache des Webs; das Python-Paket json ist der Übersetzer, der diese Sprache für Python verarbeitbar macht.

Importiert das JSON-Modul:

import json

JSON analysieren – Konvertieren von JSON nach Python

Wenn man über einen JSON-String verfügt, kann man diesen mithilfe der json.loads()-Methode analysieren. Das Ergebnis wird ein Dictionary sein.

Konvertieren von JSON nach Python:

import json

# Ein JSON String:
x =  '{ "name":"Peter", "alter":30, "stadt":"Rosenheim"}'

# x Parsen:
y = json.loads(x)

# Das Ergebnis ist ein Python Dictionary:
print(y["alter"])

INFO: Ein Parser ist ein Computerprogramm, das in der Informatik für die Zerlegung und Umwandlung einer Eingabe in ein für die Weiterverarbeitung geeigneteres Format zuständig ist (Übersetzer).

Konvertieren von Python nach JSON

Wenn man über ein Python-Objekt verfügt, kann man dieses mithilfe der json.dumps()Methode in einen JSON-String konvertieren.

Konvertieren von Python nach JSON:

import json

# Ein Python-Objekt (Dictionary):
x = {
  "name": "Peter",
  "alter": 30,
  "stadt": "Rosenheim"
}

# Konvertierung zu JSON:
y = json.dumps(x)

# Das Ergebnis ist ein JSON-String:
print(y)

Man kann Python-Objekte der folgenden Typen in JSON-Strings konvertieren:

PythonJSON
dictObject
listArray
tupleArray
strString
intNumber
floatNumber
Truetrue
Falsefalse
Nonenull

Beispiel

Konvertierung von Python-Objekten in JSON-Strings und die Ausgabe der Werte:

import json

print(json.dumps({"name": "Peter", "alter": 30}))
print(json.dumps(["Apfel", "Banane"]))
print(json.dumps(("Apfel", "Banane")))
print(json.dumps("Hallo"))
print(json.dumps(42))
print(json.dumps(31.76))
print(json.dumps(True))
print(json.dumps(False))
print(json.dumps(None))

Wenn man von Python nach JSON konvertiert, werden Python-Objekte in das JSON-Äquivalent (JavaScript) konvertiert.

Beispielhaftes Konvertieren eines Python-Objekts, das alle zulässigen Datentypen enthält:

import json

x = {
  "name": "Peter",
  "alter": 30,
  "verheiratet": True,
  "geschieden": False,
  "kinder": ("Anna","Maxi"),
  "tiere": None,
  "autos": [
    {"model": "BMW 320", "ps": 220},
    {"model": "VW Golf", "ps": 150}
  ]
}

print(json.dumps(x))

Formatieren des Ergebnisses

Das obige Beispiel gibt einen JSON-String aus, der jedoch nicht sehr einfach zu lesen ist, da es keine Einrückungen und Zeilenumbrüche gibt.

Die json.dumps()-Methode verfügt über Parameter, die das Ablesen des Ergebnisses erleichtern:

Hierfür wird der indent-Parameter verwendet, um die Anzahl der Einrückungen zu definieren:

json.dumps(x, indent=4)

Man kann auch Trennzeichen definieren. Der Standardwert ist („, „, „:“)
Das bedeutet, dass man ein Komma und ein Leerzeichen verwenden muss, um jedes Objekt zu trennen,
und einen Doppelpunkt und ein Leerzeichen, um Schlüssel von Werten zu trennen.

Verwendung des separators-Parameter, um das Standardtrennzeichen zu ändern:

json.dumps(x, indent=4, separators=(". ", " = "))

Ordnen des Ergebnisses & Ausgabe von Zeichen

Die json.dumps()Methode verfügt über Parameter zum Ordnen der Schlüssel im Ergebnis:

Verwendung des sort_keys-Parameter, um anzugeben, ob das Ergebnis sortiert werden soll oder nicht:

json.dumps(x, indent=4, sort_keys=True)

Die ensure_ascii-Parameter werden verwendet, um Sonderzeichen auch als solche abzuspeichern. Der Standardwert ist „True“, was dazu führt, dass Nicht-ASCII-Zeichen mit einem Escape-Zeichen (\) versehen.

Beispiel:

json.dumps(x, ensure_ascii=True)
# Ergebnis bei der Ausgabe von "Müller" = "M\u00fcller"

json.dumps(x, ensure_ascii=False)
# Ergebnis bei der Ausgabe von "Müller" = "Müller"

Aufgabe

  • Baue eine Klasse, welche die Eingaben von „Name“, „Beruf“, „E-Mail-Adresse“ in einem JSON-String speichert und um weitere Einträge geränzt, wenn man eine neue Eingabe starten möchte
  • Baue eine weitere Klasse, welche die Daten aus dem zuvor erzeugten JSON-String ausliest und in einer Python-Ausgabe darstellt.

Übungen Objekte und Klassen

  1. Einfache Klasse erstellen Erstelle eine Klasse namens Person, die zwei Attribute hat: name und alter. Die Klasse sollte auch eine Methode namens vorstellen haben, die den Namen und das Alter der Person ausgibt.
  2. Konstruktor verwenden Erweitern Sie die Person-Klasse um einen Konstruktor, der den Namen und das Alter der Person beim Erstellen eines neuen Objekts initialisiert.
  3. Klasse Tier Erstelle eine Klasse namens Tier mit den Attributen art und laut. Die Klasse sollte eine Methode namens machenLaut haben, die den Laut des Tieres ausgibt.
  4. Vererbung Erstelle eine Klasse namens Student, die von der Person-Klasse erbt und ein zusätzliches Attribut namens matrikelnummer hat.
  5. Überschreiben von Methoden Überschreibe die vorstellen-Methode in der Student-Klasse, sodass sie auch die Matrikelnummer des Studenten ausgibt.
  6. Klasse Auto Erstelle eine Klasse namens Auto mit den Attributen marke, modell und kilometerstand. Füge Methoden hinzu, um den Kilometerstand zu erhöhen und den aktuellen Kilometerstand auszugeben.
  7. Private Attribute Ändere die Auto-Klasse, sodass der kilometerstand ein privates Attribut ist. Füge Methoden hinzu, um den Kilometerstand sicher zu setzen und zu erhalten.
  8. Klasse Buch Erstelle eine Klasse namens Buch mit den Attributen titel, autor und seitenzahl. Füge eine Methode hinzu, die das Buch vorstellt.
  9. Klassenmethoden und statische Methoden Füge der Person-Klasse eine Klassenmethode hinzu, die die Anzahl der erstellten Personenobjekte zählt. Füge auch eine statische Methode hinzu, die einen beliebigen Gruß ausgibt.
  10. Zusammensetzung Erstelle eine Klasse namens Bibliothek, die eine Liste von Buch-Objekten enthält. Füge Methoden hinzu, um Bücher hinzuzufügen und zu entfernen.

Übungen Vererbung

  1. Mehrstufige Vererbung Erstelle eine Klasse Dozent, die von der Klasse Person erbt. Füge dieser Klasse ein Attribut fachgebiet hinzu. Erstelle dann eine Klasse Professor, die von der Klasse Dozent erbt und ein zusätzliches Attribut publikationen (eine Liste von Publikationstiteln) hat.
  2. Überschreiben und Erweitern von Methoden Erweitere die Klasse Student um eine Methode studieren, die einen Text wie „Ich studiere gerade…“ ausgibt. Überschreibe diese Methode in einer neuen Klasse Doktorand, die von Student erbt, sodass sie „Ich forsche gerade…“ ausgibt. Füge dem Doktorand auch ein Attribut forschungsgebiet hinzu.
  3. Verwendung von super() In der Klasse Professor (aus Aufgabe 11) überschreibe die Methode vorstellen, sodass sie zusätzlich zu den Informationen der Elternklassen auch die Anzahl der Publikationen ausgibt. Verwende die super()-Funktion, um den Code der Elternklasse nicht zu duplizieren.
  4. Mehrere Vererbung Erstelle zwei Klassen: Forscher mit einem Attribut forschungsfeld und Schriftsteller mit einem Attribut veroeffentlichteBuecher. Erstelle dann eine Klasse Wissenschaftsautor, die von beiden Klassen erbt und beide Attribute kombiniert.
  5. Abstrakte Klassen und Methoden Erstelle eine abstrakte Klasse Fahrzeug mit einer abstrakten Methode fahren. Leite zwei Klassen davon ab: Auto und Fahrrad. Implementiere die Methode fahren in beiden Klassen, sodass sie jeweils einen passenden Text ausgeben, z.B. „Das Auto fährt“ und „Das Fahrrad fährt“.

Weitere Übungen

# PYTHON-PRÜFUNG – Klassen, Objekte, Vererbung, JSON
# Name: ____________________________    Datum: ____________________________
#
# Quellen (ausschließlich verwendet, Inhalte paraphrasiert):
# - 11. Unterrichtsblock: Arrays (Listen), Klassen & Objekte, __init__, self, __str__, f-Strings, Objektmethoden,
#   Attribute ändern/löschen, pass (pythonentwicklerkurs.de/1-2-semester/11-unterrichtsblock/)
# - 12. Unterrichtsblock: Vererbung (Kind-/Elternklasse), Methodenüberschreibung, super(), Mehrfachvererbung (Beispielidee),
#   JSON (json.loads/json.dumps, ensure_ascii) (pythonentwicklerkurs.de/1-2-semester/12-unterrichtsblock/)
#
# Verteilung (erfüllt):
# - Mindestens 8 Fragen aus Seite 1 (Block 11)
# - Mindestens 8 Fragen aus Seite 2 (Block 12)
# - Mindestens 4 kombinierte Fragen (beide Blöcke)
#
# Hinweise:
# - Benutze keine externen Bibliotheken und keine KI-Inhalte. Nur Standard-Python.
# - Verwende sprechende Variablennamen und füge kurze Kommentare hinzu.
#
# ================================
#   AUFGABEN (20 Fragen)
# ================================
#
# --- Block 11: Klassen & Objekte, Listen als Arrays (8 Aufgaben) ---
#
# 1) Klasse & f-String:
#    Implementiere eine Klasse Kunde mit den Attributen name (str) und alter (int).
#    Füge eine Methode vorstellung() hinzu, die mit einem f-String eine Begrüßung ausgibt.
#
# 2) __str__-Darstellung:
#    Erstelle eine Klasse Ticket mit feldern kategorie (str) und preis (float).
#    Implementiere __str__ so, dass print(ticket) eine sinnvolle Textzeile liefert.
#
# 3) self-Parameter umbenennen:
#    Erzeuge eine Klasse Werkzeug, in der der erste Methodenparameter NICHT self heißt (z. B. ding).
#    Zeige mit einer Instanz, dass der Code trotzdem funktioniert.
#
# 4) Objektliste (Komposition):
#    Erzeuge mehrere Produkt-Objekte (Klasse Produkt: titel, bruttopreis) und speichere sie in einer Liste.
#    Gib mit einer Schleife alle Produkte formatiert aus (gerne über __str__). Nutze len() für die Anzahl.
#
# 5) Attribute ändern & löschen:
#    Erzeuge ein Objekt Mitarbeiter (klasse mit name, rolle). Ändere die rolle nachträglich.
#    Lösche danach das Attribut rolle. Fange einen möglichen Fehler beim Zugriff darauf sauber ab.
#
# 6) Objektmethode:
#    Baue eine Klasse Dozent mit name und fach. Füge eine Methode begruesse() hinzu, die eine kurze Ausgabe macht.
#
# 7) pass in Klassen:
#    Lege eine leere Platzhalter-Klasse Baustelle an, die aktuell nur pass enthält. Erzeuge ein Objekt davon.
#
# 8) Mini-Taschenrechner-Objekt:
#    Implementiere eine Klasse Rechner mit Methoden add, sub, mul, div (je zwei Zahlen). Teste jede Methode kurz.
#
# --- Block 12: Vererbung & JSON (8 Aufgaben) ---
#
# 9) Einfache Vererbung:
#    Basisklasse Geraet mit hersteller (str). Unterklasse Smartphone mit zusaetzlich modell (str) und details()-Methode.
#    Erzeuge ein Objekt Smartphone und rufe details() auf.
#
# 10) Methodenüberschreibung:
#     Basisklasse Musikinstrument mit methode spielen(). Unterklasse Klavier überschreibt spielen() mit eigener Ausgabe.
#     Zeige den Unterschied per Objektaufruf.
#
# 11) super() verwenden:
#     Basisklasse Begruesser mit hallo(). Unterklasse HerzlichBegruesser ruft in hallo() zuerst die Elternmethode per super()
#     und ergänzt dann eigenen Text.
#
# 12) Mehrfachvererbung (einfaches Mixin):
#     Basisklasse Fahrzeug mit bauart (str). Mixin-Klasse GPSFaehig mit methode position() (z. B. feste Demo-Position).
#     Unterklasse Auto(Fahrzeug, GPSFaehig). Erzeuge ein Auto und rufe sowohl bauart-Info als auch position() auf.
#
# 13) Vererbung mit Zusatzmethode:
#     Basisklasse Form mit name (str). Unterklasse Rechteck mit breite und hoehe sowie methode flaeche().
#     Erzeuge ein Rechteck und gib die Fläche aus.
#
# 14) JSON laden (json.loads):
#     Gegeben ist ein JSON-String (eigene kleine Beispielstruktur, z. B. Person mit stadt).
#     Parse ihn in ein dict und gib mindestens einen Wert aus.
#
# 15) JSON erzeugen (json.dumps, ensure_ascii=False):
#     Erzeuge ein Dictionary mit einem Namen mit Umlaut (z. B. Müller). Gib zwei Varianten aus:
#     a) json.dumps(..., ensure_ascii=True)   b) json.dumps(..., ensure_ascii=False)
#
# 16) Klasse -> JSON:
#     Implementiere eine Klasse Kontakt mit feldern name, email und einer methode to_json(), die ein JSON-String liefert.
#
# --- Kombination beider Blöcke (4 Aufgaben) ---
#
# 17) Klassen + Liste:
#     Erzeuge eine Klasse Kurs mit titel (str) und eine Liste teilnehmer (list[str]) in __init__.
#     Füge methoden teilnehmer_hinzu(name) und zeige_teilnehmer(). Teste das mit 3 Namen.
#
# 18) Vererbung + f-Strings:
#     Basisklasse Nutzer(name). Unterklasse Student(name, matrikel). Implementiere __str__ mit f-String.
#     Lege ein Objekt an und gib es direkt mit print(...) aus.
#
# 19) JSON -> Objekte:
#     Erzeuge einen JSON-String mit einer Liste von Artikeln (jeweils titel und preis).
#     Parse ihn und erzeuge für jedes Element ein Produkt-Objekt aus Aufgabe 4. Gib alle Produkte aus.
#
# 20) super() + JSON:
#     Basisklasse Kunde (wie in Aufgabe 1). Unterklasse PremiumKunde mit extra bonuspunkte (int).
#     Überschreibe __str__ (nutze super()) und füge eine methode export(), die die wichtigsten Felder als JSON liefert.
#
# Viel Erfolg!

Lösungen (bei der Lehrkraft anfragen)

Weitere Übungen Objektorientierte Programmierung: https://informatik.bildung-rp.de/fileadmin/user_upload/informatik.bildung-rp.de/Fortbildung/pdf/PY-110831-Savoric-OOP.pdf