it-praxisportal/15_parameter_und_container/index.html

316 lines
19 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapitel 15: Parameter (*args, **kwargs) & Eigene Container</title>
<link rel="stylesheet" href="../assets/style.css">
</head>
<body>
<header>
<div class="header-container">
<nav class="nav-links">
<a href="../index.html">🏠 Zur Kursübersicht</a>
<span>&rsaquo;</span>
<a href="../14_gui_mit_tkinter/index.html">Kapitel 14</a>
<span>&rsaquo;</span>
<span style="color: white; font-weight: 600;">Kapitel 15</span>
</nav>
<div class="header-badge">Kapitel 15 &bull; Schulabgleich 29.0 &amp; 30.0</div>
<h1>Parameter (*args, **kwargs) &amp; Eigene Container 🎒📦</h1>
<p>Mache Funktionen maximal flexibel mit variablen Argumenten und baue eigene Datenstrukturen mit den magischen Container-Dunder-Methoden von Python.</p>
</div>
</header>
<main class="container">
<!-- 1. Das Konzept: Die Werkzeugkoffer- & Rucksack-Analogie -->
<section class="card">
<h2>1. Warum flexible Parameter &amp; eigene Container?</h2>
<p>
In einfachen Programmen hat jede Funktion eine starre Anzahl von Parametern (z.B. <code>def addiere(a, b)</code>).
In realen Software-Projekten möchten wir jedoch oft:
</p>
<ul>
<li>Funktionen aufrufen, die <strong>beliebig viele Argumente</strong> annehmen (wie <code>print()</code> oder <code>max()</code>).</li>
<li>Optionale Konfigurationen und Attribute flexibel übergeben (wie bei Profilen oder Einstellungen).</li>
<li>Eigene Klassen bauen, die sich wie <strong>Listen oder Dictionaries</strong> anfühlen (mit <code>len()</code>, <code>[index]</code>, <code>in</code> und <code>for</code>-Schleifen).</li>
</ul>
<div class="box box-analogie">
<div class="box-title">🧰 Die Werkzeugkoffer- &amp; Rucksack-Analogie:</div>
<ul>
<li><strong><code>*args</code> (Der Werkzeuggürtel):</strong> Du packst beliebig viele Werkzeuge hinein. Python schnürt sie automatisch zu einem unveränderlichen <strong>Tupel</strong> zusammen.</li>
<li><strong><code>**kwargs</code> (Das Etiketten-Fach):</strong> Du übergibst Dinge mit Beschriftung (z.B. <code>schluessel=wert</code>). Python bündelt sie zu einem <strong>Dictionary</strong>.</li>
<li><strong>Container-Klasse (Der smarte Rucksack):</strong> Dein Rucksack weiß, wie schwer er maximal sein darf, wie viele Gegenstände hineinpassen (<code>len()</code>), lässt dich per Griff hineinfassen (<code>[0]</code> oder <code>["Schwert"]</code>) und prüft, ob Proviant eingepackt ist (<code>in</code>).</li>
</ul>
</div>
</section>
<!-- 2. Parameter-Cheat-Sheet -->
<section class="card">
<h2>2. Cheat-Sheet: *args, **kwargs &amp; Unpacking</h2>
<p>
Python unterscheidet zwischen dem <strong>Packen</strong> (in der Funktionsdefinition) und dem <strong>Entpacken</strong> (beim Funktionsaufruf):
</p>
<table style="width: 100%; border-collapse: collapse; margin: 15px 0;">
<thead>
<tr style="background: var(--primary-light); text-align: left;">
<th style="padding: 10px; border: 1px solid var(--border-color);">Syntax</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Typ in Python</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Bedeutung &amp; Verwendung</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Code-Beispiel</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>*args</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>tuple</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Nimmt beliebig viele Positionsargumente entgegen.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>def f(*zahlen): return sum(zahlen)</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>**kwargs</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>dict</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Nimmt beliebige Schlüsselwortargumente (<code>k=v</code>) entgegen.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>def f(**opt): return opt.get("lang", "de")</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>*liste</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Unpacking</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Entpackt Elemente einer Liste/Sequenz als einzelne Argumente.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>werte = [10, 20]; f(*werte)</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>**dict</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Unpacking</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Entpackt Dictionary-Paare als benannte Schlüsselwortargumente.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>daten = {"alter": 25}; f(**daten)</code></td>
</tr>
</tbody>
</table>
<div class="box box-tipp">
<div class="box-title">⚠️ Die feste Parameter-Reihenfolge in Funktionsköpfen:</div>
<code>def meine_funktion(pos1, pos2, *args, standard_option=True, **kwargs):</code>
<ol style="margin-left: 20px; margin-top: 6px;">
<li>Feste Positionsargumente (<code>pos1</code>, <code>pos2</code>)</li>
<li>Variable Positionsargumente (<code>*args</code>)</li>
<li>Optionale Argumente mit Default-Werten / Keyword-Only (<code>standard_option=True</code>)</li>
<li>Variable Keyword-Argumente (<code>**kwargs</code>)</li>
</ol>
</div>
</section>
<!-- 3. Container-Dunder Protokoll -->
<section class="card">
<h2>3. Das Python Container-Protokoll: Magische Dunder-Methoden</h2>
<p>
Damit sich eine eigene Klasse wie ein nativer Python-Container verhält, überschreiben wir die speziellen <strong>Double-Underscore (Dunder)</strong> Methoden:
</p>
<table style="width: 100%; border-collapse: collapse; margin: 15px 0;">
<thead>
<tr style="background: var(--primary-light); text-align: left;">
<th style="padding: 10px; border: 1px solid var(--border-color);">Dunder-Methode</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Auslöser (Python-Syntax)</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Rückgabe / Verhalten</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Fehler bei Ungültigkeit</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>__len__(self)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>len(container)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Gibt die Anzahl der Elemente als <code>int &gt;= 0</code> zurück.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>TypeError</code> bei nicht-int</td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>__getitem__(self, key)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>container[key]</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Liest Element an Index <code>key</code> (z.B. int oder str-Name).</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>IndexError</code> / <code>KeyError</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>__setitem__(self, key, val)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>container[key] = val</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Ersetzt oder setzt das Element an Position <code>key</code>.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>IndexError</code> / <code>ValueError</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>__delitem__(self, key)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>del container[key]</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Entfernt das Element an Position oder mit Name <code>key</code>.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>IndexError</code> / <code>KeyError</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>__contains__(self, item)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>item in container</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Gibt <code>True</code> zurück, wenn Element existiert, sonst <code>False</code>.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">-</td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>__iter__(self)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>for x in container:</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Gibt einen Iterator zurück (z.B. <code>iter(self._items)</code>).</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">-</td>
</tr>
</tbody>
</table>
</section>
<!-- 4. Das RPG Gaming-Inventar -->
<section class="card">
<h2>4. Das Gaming-Inventar im Detail</h2>
<p>
In dieser Übung implementierst du ein vollwertiges Rollenspiel-Inventar:
</p>
<div style="background: #1e293b; color: #f8fafc; border-radius: var(--radius-md); padding: 20px; font-family: var(--font-mono); font-size: 0.9rem; line-height: 1.6; margin: 15px 0;">
<div style="color: #60a5fa; font-weight: bold; margin-bottom: 8px;">🎒 Klasse: Inventar (max_gewicht=15.0 kg)</div>
<div style="padding-left: 15px; border-left: 2px solid #3b82f6;">
<div>├── [0] <span style="color: #a7f3d0;">Eisenschwert</span> (4.5 kg, 120 G)</div>
<div>├── [1] <span style="color: #a7f3d0;">Holzschild</span> (3.0 kg, 45 G)</div>
<div>└── [2] <span style="color: #a7f3d0;">Heiltrank</span> (0.5 kg, 20 G)</div>
</div>
<div style="margin-top: 12px; color: #fbbf24;">
📊 Status: 3 Gegenstände | Gewicht: 8.0 / 15.0 kg (Frei: 7.0 kg)
</div>
<div style="margin-top: 8px; color: #94a3b8; font-size: 0.85rem;">
&bull; <code>inv["Heiltrank"]</code> &rarr; Findet Trank per Namen!<br>
&bull; <code>"Holzschild" in inv</code> &rarr; True<br>
&bull; <code>inv.hinzufuegen(*items)</code> &rarr; Prüft Maximalgewicht automatisch!
</div>
</div>
</section>
<!-- 5. Interaktive Checkliste -->
<section class="card">
<h2>5. 📋 Deine Aufgaben-Checkliste</h2>
<p>Öffne die Datei <code>15_parameter_und_container/aufgabe.py</code> und hake die Schritte nach und nach ab:</p>
<ul class="task-list">
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t15_1">
<label for="t15_1" class="task-label">
<strong>TODO 1 (*args Rabatt-Rechner):</strong> <code>berechne_gesamtsumme(*zahlen, rabatt_prozent=0.0)</code> mit Rabattprüfung (0 bis 100) und Rundung auf 2 Stellen.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t15_2">
<label for="t15_2" class="task-label">
<strong>TODO 2 (**kwargs Profil):</strong> <code>erstelle_profil(name, **details)</code> zur flexiblen Dictionary-Erzeugung.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t15_3">
<label for="t15_3" class="task-label">
<strong>TODO 3 (Klasse Gegenstand):</strong> <code>__init__</code> mit Validierung (Gewicht/Wert >= 0), <code>__repr__</code>, <code>__str__</code> und <code>__eq__</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t15_4">
<label for="t15_4" class="task-label">
<strong>TODO 4 &amp; 5 (Inventar Basis):</strong> <code>__init__</code> (Maximalgewicht > 0), Properties <code>gesamtgewicht</code> und <code>freie_kapazitaet</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t15_5">
<label for="t15_5" class="task-label">
<strong>TODO 6 (hinzufuegen mit *args):</strong> Mehrere Gegenstände auf einmal hinzufügen, Typen validieren und Maximalgewicht überwachen.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t15_6">
<label for="t15_6" class="task-label">
<strong>TODO 7 &amp; 8 (__len__ &amp; __getitem__):</strong> <code>len(inv)</code> und Multi-Lookup für Index (int) &amp; Name (str).
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t15_7">
<label for="t15_7" class="task-label">
<strong>TODO 9 &amp; 10 (__setitem__ &amp; __delitem__):</strong> Element-Austausch mit Gewichtsprüfung und Löschen per Index/Name.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t15_8">
<label for="t15_8" class="task-label">
<strong>TODO 11, 12 &amp; 13 (__contains__, __iter__, Helper):</strong> <code>in</code>-Operator, <code>for</code>-Iteration und <code>packe_inventar(*items)</code>.
</label>
</li>
</ul>
<h3>💡 Lösungshinweise (Aufklappen bei Bedarf)</h3>
<details>
<summary>🔍 Tipp zu TODO 8: __getitem__ mit int und str unterstützen</summary>
<div class="details-content">
<p>Prüfe mit <code>isinstance()</code>, welcher Suchtyp übergeben wurde:</p>
<div class="code-container">
<pre><span class="kw">def</span> <span class="fn">__getitem__</span>(<span class="var">self</span>, <span class="var">key</span>):
<span class="kw">if</span> <span class="fn">isinstance</span>(<span class="var">key</span>, <span class="fn">int</span>):
<span class="kw">return</span> <span class="var">self</span>.<span class="attr">_items</span>[<span class="var">key</span>] <span class="cm"># Wirft automatisch IndexError bei ungültigem Index</span>
<span class="kw">elif</span> <span class="fn">isinstance</span>(<span class="var">key</span>, <span class="fn">str</span>):
<span class="kw">for</span> <span class="var">g</span> <span class="kw">in</span> <span class="var">self</span>.<span class="attr">_items</span>:
<span class="kw">if</span> <span class="var">g</span>.<span class="attr">name</span>.<span class="fn">lower</span>() == <span class="var">key</span>.<span class="fn">lower</span>():
<span class="kw">return</span> <span class="var">g</span>
<span class="kw">raise</span> <span class="fn">KeyError</span>(<span class="str">f"Gegenstand '{key}' nicht im Inventar gefunden!"</span>)
<span class="kw">else</span>:
<span class="kw">raise</span> <span class="fn">TypeError</span>(<span class="str">"Index muss int oder str sein!"</span>)</pre>
</div>
</div>
</details>
<details>
<summary>🔍 Tipp zu TODO 9: Gewichtsberechnung bei __setitem__</summary>
<div class="details-content">
<p>Beim Ersetzen eines Elements musst du das Gewicht des alten Elements abziehen und das neue hinzurechnen:</p>
<div class="code-container">
<pre><span class="var">altes_gewicht</span> = <span class="var">self</span>.<span class="attr">_items</span>[<span class="var">index</span>].<span class="attr">gewicht</span>
<span class="var">neues_gesamtgewicht</span> = <span class="var">self</span>.<span class="attr">gesamtgewicht</span> - <span class="var">altes_gewicht</span> + <span class="var">value</span>.<span class="attr">gewicht</span>
<span class="kw">if</span> <span class="var">neues_gesamtgewicht</span> > <span class="var">self</span>.<span class="attr">max_gewicht</span>:
<span class="kw">raise</span> <span class="fn">ValueError</span>(<span class="str">"Austausch überschreitet Maximalgewicht!"</span>)
<span class="var">self</span>.<span class="attr">_items</span>[<span class="var">index</span>] = <span class="var">value</span></pre>
</div>
</div>
</details>
</section>
<!-- 6. Testen & Navigation -->
<section class="card">
<h2>6. 🧪 Lösung überprüfen &amp; Testen</h2>
<h3>1. Automatische Tests ausführen:</h3>
<div class="code-container">
<div class="code-header">
<span>Terminal (im Ordner 15_parameter_und_container)</span>
</div>
<pre><code>python3 test_aufgabe.py</code></pre>
</div>
<h3>2. Interaktives Terminal-Skript starten:</h3>
<div class="code-container">
<div class="code-header">
<span>Terminal</span>
</div>
<pre><code>python3 aufgabe.py</code></pre>
</div>
<div class="footer-nav">
<a href="../14_gui_mit_tkinter/index.html" class="btn btn-secondary">&larr; Zurück zu Kapitel 14 (Tkinter GUI)</a>
<a href="../index.html" class="btn btn-secondary">🏠 Kursübersicht</a>
<a href="../16_master_abschlussprojekt/index.html" class="btn">Weiter zu Kapitel 16 (Master-Abschlussprojekt) &rarr;</a>
</div>
</section>
</main>
</body>
</html>