360 lines
16 KiB
Python
360 lines
16 KiB
Python
"""
|
|
Kapitel 15: Parameter (*args, **kwargs) & Eigene Container-Klassen 🎒📦
|
|
======================================================================
|
|
Aufgabe: Lerne flexible Funktionsparameter kennen und baue eine
|
|
vollwertige Gaming-Inventar-Containerklasse mit magischen Dunder-Methoden!
|
|
|
|
Themen:
|
|
1. *args (beliebig viele Positionsargumente als Tupel)
|
|
2. **kwargs (beliebig viele Schlüsselwortargumente als Dictionary)
|
|
3. Argument-Unpacking (*liste, **dict)
|
|
4. Eigene Container mit __len__, __getitem__, __setitem__, __delitem__, __contains__, __iter__
|
|
"""
|
|
|
|
import math
|
|
from typing import Union, List, Optional, Dict, Any, Iterator
|
|
|
|
|
|
# ==============================================================================
|
|
# TEIL 1: FUNKTIONEN MIT *ARGS UND **KWARGS
|
|
# ==============================================================================
|
|
|
|
def berechne_gesamtsumme(*zahlen: float, rabatt_prozent: float = 0.0) -> float:
|
|
"""
|
|
Berechnet die Gesamtsumme beliebig vieler Zahlen (*args) und wendet
|
|
einen optionalen prozentualen Rabatt an.
|
|
|
|
Parameter:
|
|
*zahlen (float): Beliebig viele numerische Werte (als Tupel empfangen).
|
|
rabatt_prozent (float): Rabatt in Prozent (0.0 bis 100.0). Standard ist 0.0.
|
|
|
|
Rückgabe:
|
|
float: Die berechnete Gesamtsumme nach Rabatt, gerundet auf 2 Nachkommastellen.
|
|
|
|
Exceptions:
|
|
ValueError: Falls rabatt_prozent < 0.0 oder > 100.0 ist.
|
|
|
|
Beispiele:
|
|
berechne_gesamtsumme(10.0, 20.0, 30.0) -> 60.0
|
|
berechne_gesamtsumme(100.0, 50.0, rabatt_prozent=10.0) -> 135.0
|
|
berechne_gesamtsumme() -> 0.0
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 1: Implementiere berechne_gesamtsumme
|
|
# 1. Validiere rabatt_prozent: Liegt der Wert nicht zwischen 0.0 und 100.0,
|
|
# löse einen ValueError mit passender Nachricht aus.
|
|
# 2. Summiere alle Werte in `zahlen` (wenn zahlen leer ist, ist die Summe 0.0).
|
|
# 3. Ziehe den Rabatt ab: summe * (1.0 - rabatt_prozent / 100.0)
|
|
# 4. Runde das Ergebnis mit round(..., 2) und gib es zurück.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
|
|
def erstelle_profil(name: str, **details: Any) -> Dict[str, Any]:
|
|
"""
|
|
Erstellt ein Profil-Dictionary mit dem Namen und beliebigen weiteren
|
|
Schlüsselwort-Attributen (**kwargs).
|
|
|
|
Parameter:
|
|
name (str): Der Name der Person oder Spielfigur.
|
|
**details (Any): Beliebige Zusatzinformationen (z.B. alter=25, klasse="Krieger").
|
|
|
|
Rückgabe:
|
|
Dict[str, Any]: Ein Dictionary, das {"name": name} enthält, ergänzt um
|
|
alle übergebenen Keyword-Argumente.
|
|
|
|
Beispiele:
|
|
erstelle_profil("Aragorn", klasse="Waldlaeufer", level=20)
|
|
-> {"name": "Aragorn", "klasse": "Waldlaeufer", "level": 20}
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 2: Implementiere erstelle_profil
|
|
# 1. Erstelle ein Dictionary mit {"name": str(name)}.
|
|
# 2. Füge alle Schlüssel-Wert-Paare aus `details` hinzu.
|
|
# 3. Gib das fertige Dictionary zurück.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
|
|
# ==============================================================================
|
|
# TEIL 2: DIE DATENKLASSE GEGENSTAND (ITEM)
|
|
# ==============================================================================
|
|
|
|
class Gegenstand:
|
|
"""
|
|
Repräsentiert einen Gegenstand im Spielinventar.
|
|
"""
|
|
|
|
def __init__(self, name: str, gewicht: float, wert: int = 0):
|
|
"""
|
|
Initialisiert einen Gegenstand.
|
|
|
|
Parameter:
|
|
name (str): Bezeichnung des Gegenstands.
|
|
gewicht (float): Gewicht in Kilogramm (muss >= 0 sein).
|
|
wert (int): Verkaufswert in Goldstücken (muss >= 0 sein, Standard 0).
|
|
|
|
Exceptions:
|
|
ValueError: Falls gewicht < 0 oder wert < 0 ist.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 3: Implementiere Gegenstand.__init__, __repr__, __str__ und __eq__
|
|
# 1. Validiere gewicht >= 0 und wert >= 0 (sonst ValueError).
|
|
# 2. Setze self.name = str(name), self.gewicht = float(gewicht), self.wert = int(wert).
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def __repr__(self) -> str:
|
|
"""
|
|
Entwickler-Darstellung, z.B. "Gegenstand(name='Heiltrank', gewicht=0.5, wert=25)"
|
|
"""
|
|
pass
|
|
|
|
def __str__(self) -> str:
|
|
"""
|
|
Benutzerfreundliche Darstellung, z.B. "Heiltrank (0.5kg, 25G)"
|
|
"""
|
|
pass
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
"""
|
|
Zwei Gegenstände sind gleich, wenn Name, Gewicht und Wert übereinstimmen.
|
|
"""
|
|
pass
|
|
|
|
|
|
# ==============================================================================
|
|
# TEIL 3: DIE EIGENE CONTAINER-KLASSE INVENTAR
|
|
# ==============================================================================
|
|
|
|
class Inventar:
|
|
"""
|
|
Eine maßgeschneiderte Container-Klasse für ein Gaming-Rucksack-Inventar.
|
|
Unterstützt Maximalgewicht, *args-Hinzufügen und alle Python-Container-Dunder!
|
|
"""
|
|
|
|
def __init__(self, max_gewicht: float = 20.0, gegenstaende: Optional[List[Gegenstand]] = None):
|
|
"""
|
|
Initialisiert das Inventar mit einem Maximalgewicht und optionalen Start-Gegenständen.
|
|
|
|
Parameter:
|
|
max_gewicht (float): Maximal tragbares Gewicht (muss > 0 sein, Standard 20.0).
|
|
gegenstaende (Optional[List[Gegenstand]]): Optionale Liste von Gegenständen.
|
|
|
|
Exceptions:
|
|
ValueError: Falls max_gewicht <= 0 oder die Start-Gegenstände das Maximalgewicht überschreiten.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 4: Inventar-Initialisierung
|
|
# 1. Validiere max_gewicht > 0 (sonst ValueError).
|
|
# 2. Speichere self.max_gewicht = float(max_gewicht).
|
|
# 3. Initialisiere eine interne Liste self._items = [].
|
|
# 4. Falls `gegenstaende` übergeben wurden, füge sie mit self.hinzufuegen(*gegenstaende) hinzu.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
@property
|
|
def gesamtgewicht(self) -> float:
|
|
"""
|
|
Berechnet das aktuelle Gesamtgewicht aller enthaltenen Gegenstände,
|
|
gerundet auf 2 Nachkommastellen.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 5a: Berechne Gesamtgewicht (Summe aller item.gewicht)
|
|
# ==========================================================================
|
|
pass
|
|
|
|
@property
|
|
def freie_kapazitaet(self) -> float:
|
|
"""
|
|
Gibt das noch verfügbare Restgewicht bis zum Maximalgewicht zurück.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 5b: Berechne freie Kapazität (max_gewicht - gesamtgewicht, gerundet auf 2 Stellen)
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def hinzufuegen(self, *items: Gegenstand) -> None:
|
|
"""
|
|
Fügt ein oder mehrere Gegenstände (*args) zum Inventar hinzu.
|
|
|
|
Parameter:
|
|
*items (Gegenstand): Beliebig viele Gegenstand-Objekte.
|
|
|
|
Exceptions:
|
|
TypeError: Falls ein Argument kein `Gegenstand`-Objekt ist.
|
|
ValueError: Falls das Hinzufügen aller übergebenen Gegenstände
|
|
das Maximalgewicht des Inventars überschreiten würde.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 6: hinzufuegen mit *args
|
|
# 1. Prüfe für jedes item in `items`, ob es eine Instanz von Gegenstand ist (sonst TypeError).
|
|
# 2. Berechne das Zusatzgewicht aller neuen Gegenstände.
|
|
# 3. Wenn self.gesamtgewicht + zusatz_gewicht > self.max_gewicht:
|
|
# Löse einen ValueError("Maximalgewicht überschritten!") aus.
|
|
# 4. Hänge alle items an self._items an.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def __len__(self) -> int:
|
|
"""
|
|
Ermöglicht len(inventar) -> Gibt die Anzahl der Gegenstände zurück.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 7: __len__
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def __getitem__(self, key: Union[int, str]) -> Gegenstand:
|
|
"""
|
|
Ermöglicht Index- und Namenszugriff:
|
|
- inventar[0] -> Gibt den Gegenstand an Position 0 zurück (int-Index).
|
|
- inventar["Heiltrank"] -> Sucht den ersten Gegenstand mit diesem Namen (case-insensitive).
|
|
|
|
Exceptions:
|
|
IndexError: Bei ungültigem Integer-Index.
|
|
KeyError: Falls kein Gegenstand mit dem übergebenen String-Namen gefunden wurde.
|
|
TypeError: Falls key weder int noch str ist.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 8: __getitem__ (Multi-Type Lookup: int und str)
|
|
# 1. Wenn isinstance(key, int): gib self._items[key] zurück.
|
|
# 2. Wenn isinstance(key, str): suche den ersten Gegenstand mit passendem Namen
|
|
# (Vergleich mit g.name.lower() == key.lower()). Wenn gefunden, zurückgeben.
|
|
# Wurde kein Item gefunden, löse KeyError aus.
|
|
# 3. Andernfalls löse TypeError aus.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def __setitem__(self, index: int, value: Gegenstand) -> None:
|
|
"""
|
|
Ermöglicht Zuweisung per Index: inventar[0] = neuer_gegenstand.
|
|
|
|
Exceptions:
|
|
TypeError: Falls index kein int oder value kein Gegenstand ist.
|
|
IndexError: Falls index außerhalb des gültigen Bereichs liegt.
|
|
ValueError: Falls durch den Austausch das Maximalgewicht überschritten würde.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 9: __setitem__
|
|
# 1. Prüfe Typen (index muss int sein, value muss Gegenstand sein, sonst TypeError).
|
|
# 2. Prüfe Indexgrenzen: -len(self._items) <= index < len(self._items) (sonst IndexError).
|
|
# 3. Berechne neues Gesamtgewicht: gesamtgewicht - altes_item.gewicht + value.gewicht.
|
|
# Wenn neues_gesamtgewicht > max_gewicht: löse ValueError aus.
|
|
# 4. Ersetze das Element: self._items[index] = value.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def __delitem__(self, key: Union[int, str]) -> None:
|
|
"""
|
|
Ermöglicht das Löschen per del-Keyword:
|
|
- del inventar[0] -> Löscht per Integer-Index.
|
|
- del inventar["Heiltrank"] -> Löscht das erste gefundene Item mit diesem Namen.
|
|
|
|
Exceptions:
|
|
IndexError: Bei ungültigem int-Index.
|
|
KeyError: Wenn String-Name nicht existiert.
|
|
TypeError: Falls key weder int noch str ist.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 10: __delitem__
|
|
# 1. Wenn int: del self._items[key].
|
|
# 2. Wenn str: suche erstes Element mit passendem Namen (case-insensitive) und entferne es.
|
|
# Falls nicht vorhanden -> KeyError.
|
|
# 3. Andernfalls -> TypeError.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def __contains__(self, item: Union[Gegenstand, str]) -> bool:
|
|
"""
|
|
Ermöglicht den `in`-Operator:
|
|
- "Heiltrank" in inventar -> True/False (Name-Suche, case-insensitive)
|
|
- gegenstand_objekt in inventar -> True/False (Objekt-Prüfung)
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 11: __contains__
|
|
# 1. Wenn str: prüfe, ob ein Gegenstand mit diesem Namen existiert (case-insensitive).
|
|
# 2. Wenn Gegenstand: prüfe `item in self._items`.
|
|
# 3. Andernfalls gebe False zurück.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def __iter__(self) -> Iterator[Gegenstand]:
|
|
"""
|
|
Ermöglicht Iteration: for item in inventar: ...
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 12: __iter__
|
|
# Gib einen Iterator über self._items zurück (z.B. iter(self._items)).
|
|
# ==========================================================================
|
|
pass
|
|
|
|
def __repr__(self) -> str:
|
|
"""Entwickler-Darstellung."""
|
|
return f"Inventar(max_gewicht={self.max_gewicht}, gegenstaende={self._items!r})"
|
|
|
|
def __str__(self) -> str:
|
|
"""Benutzerfreundliche Zusammenfassung."""
|
|
return f"Inventar ({len(self)} Gegenstände, {self.gesamtgewicht:.1f}/{self.max_gewicht:.1f} kg)"
|
|
|
|
|
|
# ==============================================================================
|
|
# TEIL 4: UNPACKING-HELPER-FUNKTION
|
|
# ==============================================================================
|
|
|
|
def packe_inventar(*gegenstaende: Gegenstand, max_gewicht: float = 20.0) -> Inventar:
|
|
"""
|
|
Erstellt ein neues Inventar und packt alle übergebenen Gegenstände (*args) hinein.
|
|
Demonstriert Argument-Unpacking und flexible Parameter.
|
|
"""
|
|
# ==========================================================================
|
|
# TODO 13: packe_inventar
|
|
# 1. Erstelle ein Inventar mit `max_gewicht=max_gewicht`.
|
|
# 2. Füge alle `gegenstaende` mit inv.hinzufuegen(*gegenstaende) hinzu.
|
|
# 3. Gib das befüllte Inventar zurück.
|
|
# ==========================================================================
|
|
pass
|
|
|
|
|
|
# ==============================================================================
|
|
# INTERAKTIVER TERMINAL-TEST
|
|
# (python3 aufgabe.py)
|
|
# ==============================================================================
|
|
if __name__ == "__main__":
|
|
print("🎒 Kapitel 15: Parameter & Eigene Container-Klassen\n" + "=" * 55)
|
|
|
|
# 1. Test *args & Rabatt
|
|
preise = [19.99, 49.99, 9.99, 29.50]
|
|
summe_rabatt = berechne_gesamtsumme(*preise, rabatt_prozent=15.0)
|
|
print(f"1. *args Summe mit 15% Rabatt: {summe_rabatt} €")
|
|
|
|
# 2. Test **kwargs Profil
|
|
held_daten = {"klasse": "Magier", "stufe": 12, "mana": 350, "element": "Feuer"}
|
|
profil = erstelle_profil("Ignis", **held_daten)
|
|
print(f"2. **kwargs Profil: {profil}")
|
|
|
|
# 3. Test Container Inventar
|
|
schwert = Gegenstand("Eisenschwert", 4.5, 120)
|
|
schild = Gegenstand("Holzschild", 3.0, 45)
|
|
trank = Gegenstand("Heiltrank", 0.5, 20)
|
|
|
|
inv = Inventar(max_gewicht=15.0)
|
|
try:
|
|
inv.hinzufuegen(schwert, schild, trank)
|
|
print(f"\n3. Inventar initialisiert: {inv}")
|
|
print(f" Anzahl Gegenstände (len): {len(inv)}")
|
|
print(f" Gesamtgewicht: {inv.gesamtgewicht} / {inv.max_gewicht} kg")
|
|
print(f" Erster Gegenstand [0]: {inv[0]}")
|
|
print(f" Suche nach Name ['Heiltrank']: {inv['Heiltrank']}")
|
|
print(f" Ist 'Holzschild' im Rucksack? {'Holzschild' in inv}")
|
|
|
|
print("\n Alle Gegenstände im Rucksack:")
|
|
for item in inv:
|
|
print(f" - {item}")
|
|
|
|
print("\n Lösche Heiltrank...")
|
|
del inv["Heiltrank"]
|
|
print(f" Neues Gesamtgewicht: {inv.gesamtgewicht} kg (Anzahl: {len(inv)})")
|
|
|
|
except Exception as e:
|
|
print(f" ⏳ Noch nicht vollständig implementiert: {e}")
|