Kapitel 15 • Schulabgleich 29.0 & 30.0

Parameter (*args, **kwargs) & Eigene Container 🎒📦

Mache Funktionen maximal flexibel mit variablen Argumenten und baue eigene Datenstrukturen mit den magischen Container-Dunder-Methoden von Python.

📂 Dateien dieser Einheit:
📄 aufgabe.py (Startercode) 🧪 test_aufgabe.py (Tests) 💡 musterloesung.py (Lösung) 📖 README.md

1. Warum flexible Parameter & eigene Container?

In einfachen Programmen hat jede Funktion eine starre Anzahl von Parametern (z.B. def addiere(a, b)). In realen Software-Projekten möchten wir jedoch oft:

  • Funktionen aufrufen, die beliebig viele Argumente annehmen (wie print() oder max()).
  • Optionale Konfigurationen und Attribute flexibel übergeben (wie bei Profilen oder Einstellungen).
  • Eigene Klassen bauen, die sich wie Listen oder Dictionaries anfühlen (mit len(), [index], in und for-Schleifen).
🧰 Die Werkzeugkoffer- & Rucksack-Analogie:
  • *args (Der Werkzeuggürtel): Du packst beliebig viele Werkzeuge hinein. Python schnürt sie automatisch zu einem unveränderlichen Tupel zusammen.
  • **kwargs (Das Etiketten-Fach): Du übergibst Dinge mit Beschriftung (z.B. schluessel=wert). Python bündelt sie zu einem Dictionary.
  • Container-Klasse (Der smarte Rucksack): Dein Rucksack weiß, wie schwer er maximal sein darf, wie viele Gegenstände hineinpassen (len()), lässt dich per Griff hineinfassen ([0] oder ["Schwert"]) und prüft, ob Proviant eingepackt ist (in).

2. Cheat-Sheet: *args, **kwargs & Unpacking

Python unterscheidet zwischen dem Packen (in der Funktionsdefinition) und dem Entpacken (beim Funktionsaufruf):

Syntax Typ in Python Bedeutung & Verwendung Code-Beispiel
*args tuple Nimmt beliebig viele Positionsargumente entgegen. def f(*zahlen): return sum(zahlen)
**kwargs dict Nimmt beliebige Schlüsselwortargumente (k=v) entgegen. def f(**opt): return opt.get("lang", "de")
*liste Unpacking Entpackt Elemente einer Liste/Sequenz als einzelne Argumente. werte = [10, 20]; f(*werte)
**dict Unpacking Entpackt Dictionary-Paare als benannte Schlüsselwortargumente. daten = {"alter": 25}; f(**daten)
⚠️ Die feste Parameter-Reihenfolge in Funktionsköpfen:
def meine_funktion(pos1, pos2, *args, standard_option=True, **kwargs):
  1. Feste Positionsargumente (pos1, pos2)
  2. Variable Positionsargumente (*args)
  3. Optionale Argumente mit Default-Werten / Keyword-Only (standard_option=True)
  4. Variable Keyword-Argumente (**kwargs)

3. Das Python Container-Protokoll: Magische Dunder-Methoden

Damit sich eine eigene Klasse wie ein nativer Python-Container verhält, überschreiben wir die speziellen Double-Underscore (Dunder) Methoden:

Dunder-Methode Auslöser (Python-Syntax) Rückgabe / Verhalten Fehler bei Ungültigkeit
__len__(self) len(container) Gibt die Anzahl der Elemente als int >= 0 zurück. TypeError bei nicht-int
__getitem__(self, key) container[key] Liest Element an Index key (z.B. int oder str-Name). IndexError / KeyError
__setitem__(self, key, val) container[key] = val Ersetzt oder setzt das Element an Position key. IndexError / ValueError
__delitem__(self, key) del container[key] Entfernt das Element an Position oder mit Name key. IndexError / KeyError
__contains__(self, item) item in container Gibt True zurück, wenn Element existiert, sonst False. -
__iter__(self) for x in container: Gibt einen Iterator zurück (z.B. iter(self._items)). -

4. Das Gaming-Inventar im Detail

In dieser Übung implementierst du ein vollwertiges Rollenspiel-Inventar:

🎒 Klasse: Inventar (max_gewicht=15.0 kg)
├── [0] Eisenschwert (4.5 kg, 120 G)
├── [1] Holzschild (3.0 kg, 45 G)
└── [2] Heiltrank (0.5 kg, 20 G)
📊 Status: 3 Gegenstände | Gewicht: 8.0 / 15.0 kg (Frei: 7.0 kg)
• inv["Heiltrank"] → Findet Trank per Namen!
• "Holzschild" in inv → True
• inv.hinzufuegen(*items) → Prüft Maximalgewicht automatisch!

5. 📋 Deine Aufgaben-Checkliste

Öffne die Datei aufgabe.py und hake die Schritte nach und nach ab:

💡 Lösungshinweise (Aufklappen bei Bedarf)

🔍 Tipp zu TODO 8: __getitem__ mit int und str unterstützen

Prüfe mit isinstance(), welcher Suchtyp übergeben wurde:

def __getitem__(self, key):
    if isinstance(key, int):
        return self._items[key]  # Wirft automatisch IndexError bei ungültigem Index
    elif isinstance(key, str):
        for g in self._items:
            if g.name.lower() == key.lower():
                return g
        raise KeyError(f"Gegenstand '{key}' nicht im Inventar gefunden!")
    else:
        raise TypeError("Index muss int oder str sein!")
🔍 Tipp zu TODO 9: Gewichtsberechnung bei __setitem__

Beim Ersetzen eines Elements musst du das Gewicht des alten Elements abziehen und das neue hinzurechnen:

altes_gewicht = self._items[index].gewicht
neues_gesamtgewicht = self.gesamtgewicht - altes_gewicht + value.gewicht
if neues_gesamtgewicht > self.max_gewicht:
    raise ValueError("Austausch überschreitet Maximalgewicht!")
self._items[index] = value

6. 🧪 Lösung überprüfen & Testen

1. Automatische Tests ausführen:

Terminal (im Ordner 15_parameter_und_container)
python3 test_aufgabe.py

2. Interaktives Terminal-Skript starten:

Terminal
python3 aufgabe.py