Aufgaben-Python/13_persistenz_json_und_csv/index.html

247 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapitel 13: Datei-Persistenz (JSON &amp; CSV)</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="../12_exceptions_und_fehlerbehandlung/index.html">Kapitel 12</a>
<span>&rsaquo;</span>
<span style="color: white; font-weight: 600;">Kapitel 13</span>
</nav>
<div class="header-badge">Kapitel 13 &bull; Schulabgleich 14.0</div>
<h1>Datei-Persistenz: JSON &amp; CSV 💾📁</h1>
<p>Lerne, wie du Daten dauerhaft auf der Festplatte speicherst, Objekte mit <code>to_dict()</code> und <code>from_dict()</code> serialisierst und Tabellen als CSV exportierst.</p>
</div>
</header>
<main class="container">
<!-- 1. Das Konzept: RAM vs Festplatte -->
<section class="card">
<h2>1. Warum brauchen wir Persistenz? (Die Tagebuch-Analogie)</h2>
<p>
Bislang haben alle unsere Programme ihre Daten nur im <strong>Arbeitsspeicher (RAM)</strong> gehalten.
Das Problem: Sobald das Python-Programm beendet wird oder der Strom ausfällt, ist der gesamte RAM blitzschnell gelöscht!
</p>
<div class="box box-analogie">
<div class="box-title">📓 Die Tagebuch-Analogie:</div>
Der Arbeitsspeicher (RAM) ist wie dein <strong>Kurzzeitgedächtnis</strong>: blitzschnell, aber nach dem Schlafen (Programmende) ist alles weg.
Die Festplatte (SSD) ist dein <strong>Tagebuch / Notizbuch</strong>: Was du einmal mit Tinte hineingeschrieben hast, bleibt auch Jahre später noch genau so stehen!
</div>
<h3>Der goldene Standard: Dateien sicher öffnen mit <code>with open()</code></h3>
<div class="code-container">
<div class="code-header">
<span>Kontext-Manager mit open()</span>
</div>
<pre><span class="cm"># 'w' = Write (Schreiben / Überschreiben)</span>
<span class="cm"># 'r' = Read (Lesen)</span>
<span class="cm"># 'a' = Append (Am Ende anhängen)</span>
<span class="kw">with</span> <span class="fn">open</span>(<span class="str">"notizen.txt"</span>, <span class="str">"w"</span>, <span class="var">encoding</span>=<span class="str">"utf-8"</span>) <span class="kw">as</span> <span class="var">datei</span>:
<span class="var">datei</span>.<span class="fn">write</span>(<span class="str">"Hallo Welt mit Umlauten: ä, ö, ü!\n"</span>)
<span class="cm"># Hier wird die Datei automatisch und sicher geschlossen – selbst wenn ein Fehler auftritt!</span></pre>
</div>
</section>
<!-- 2. JSON vs CSV Vergleich -->
<section class="card">
<h2>2. JSON vs. CSV: Welches Format für welchen Zweck?</h2>
<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);">Kriterium</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">JSON (JavaScript Object Notation)</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">CSV (Comma-Separated Values)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Ideal für...</strong></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Komplexe, verschachtelte Objekte &amp; Savegames</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Flache Tabellen (Excel, Google Sheets)</td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Verschachtelung</strong></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Ja (Listen von Objekten mit Unter-Listen)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Nein (nur Zeilen und Spalten)</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Python-Modul</strong></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>import json</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>import csv</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Hauptbefehle</strong></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>json.dump()</code> / <code>json.load()</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>csv.DictWriter</code> / <code>csv.DictReader</code></td>
</tr>
</tbody>
</table>
</section>
<!-- 3. Objekt-Serialisierung in OOP -->
<section class="card">
<h2>3. Objekt-Serialisierung: <code>to_dict()</code> und <code>from_dict()</code></h2>
<p>
Ein Python-Klassenobjekt (z.B. <code>Spieler</code>) kann nicht direkt als reiner Text gespeichert werden.
Daher übersetzen wir das Objekt in ein einfaches Dictionary (<strong>Serialisierung</strong>) und bauen es beim Laden wieder als echtes Objekt zusammen (<strong>Deserialisierung</strong>).
</p>
<div class="code-container">
<div class="code-header">
<span>Serialisierungsmuster in Klassen</span>
</div>
<pre><span class="kw">class</span> <span class="fn">Spieler</span>:
<span class="kw">def</span> <span class="fn">__init__</span>(<span class="var">self</span>, <span class="var">name</span>: <span class="fn">str</span>, <span class="var">level</span>: <span class="fn">int</span>, <span class="var">punkte</span>: <span class="fn">int</span>):
<span class="var">self</span>.<span class="attr">name</span> = <span class="var">name</span>
<span class="var">self</span>.<span class="attr">level</span> = <span class="var">level</span>
<span class="var">self</span>.<span class="attr">punkte</span> = <span class="var">punkte</span>
<span class="cm"># 1. In Dictionary umwandeln (zum Speichern)</span>
<span class="kw">def</span> <span class="fn">to_dict</span>(<span class="var">self</span>) -> <span class="fn">dict</span>:
<span class="kw">return</span> {
<span class="str">"name"</span>: <span class="var">self</span>.<span class="attr">name</span>,
<span class="str">"level"</span>: <span class="var">self</span>.<span class="attr">level</span>,
<span class="str">"punkte"</span>: <span class="var">self</span>.<span class="attr">punkte</span>
}
<span class="cm"># 2. Aus Dictionary neues Objekt instanziieren (beim Laden)</span>
<span class="kw">@classmethod</span>
<span class="kw">def</span> <span class="fn">from_dict</span>(<span class="var">cls</span>, <span class="var">daten</span>: <span class="fn">dict</span>) -> <span class="str">'Spieler'</span>:
<span class="kw">return</span> <span class="fn">cls</span>(
<span class="var">name</span>=<span class="var">daten</span>[<span class="str">"name"</span>],
<span class="var">level</span>=<span class="var">daten</span>[<span class="str">"level"</span>],
<span class="var">punkte</span>=<span class="var">daten</span>[<span class="str">"punkte"</span>]
)</pre>
</div>
<h3>Speichern und Laden mit JSON</h3>
<div class="code-container">
<div class="code-header">
<span>JSON Speichern und Laden</span>
</div>
<pre><span class="kw">import</span> <span class="var">json</span>
<span class="cm"># Speichern:</span>
<span class="kw">with</span> <span class="fn">open</span>(<span class="str">"savegame.json"</span>, <span class="str">"w"</span>, <span class="var">encoding</span>=<span class="str">"utf-8"</span>) <span class="kw">as</span> <span class="var">f</span>:
<span class="var">json</span>.<span class="fn">dump</span>(<span class="var">spieler</span>.<span class="fn">to_dict</span>(), <span class="var">f</span>, <span class="var">indent</span>=<span class="num">4</span>, <span class="var">ensure_ascii</span>=<span class="kw">False</span>)
<span class="cm"># Laden:</span>
<span class="kw">with</span> <span class="fn">open</span>(<span class="str">"savegame.json"</span>, <span class="str">"r"</span>, <span class="var">encoding</span>=<span class="str">"utf-8"</span>) <span class="kw">as</span> <span class="var">f</span>:
<span class="var">daten</span> = <span class="var">json</span>.<span class="fn">load</span>(<span class="var">f</span>)
<span class="var">geladener_spieler</span> = <span class="fn">Spieler</span>.<span class="fn">from_dict</span>(<span class="var">daten</span>)</pre>
</div>
</section>
<!-- 4. Deine Aufgaben -->
<section class="card">
<h2>4. 🎯 Deine Aufgaben in <code>aufgabe.py</code></h2>
<p>Öffne die Datei <code>13_persistenz_json_und_csv/aufgabe.py</code> und vervollständige das Savegame-System:</p>
<ul class="task-list">
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t13_1">
<label for="t13_1" class="task-label">
<strong>TODO 1:</strong> <code>Spieler</code>-Konstruktor sowie <code>to_dict(self)</code> und <code>from_dict(cls, daten)</code> mit <code>name</code>, <code>level</code>, <code>punkte</code> und <code>inventar</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t13_2">
<label for="t13_2" class="task-label">
<strong>TODO 2:</strong> <code>Spielstand</code>-Verwaltungsmethoden: <code>spieler_hinzufuegen</code>, <code>spieler_suchen</code> und <code>bester_spieler</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t13_3">
<label for="t13_3" class="task-label">
<strong>TODO 3:</strong> <code>speichern_als_json(dateipfad)</code>: Wandelt alle Spieler in Dicts um und schreibt die JSON-Datei mit <code>json.dump</code> formatiert auf die Festplatte.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t13_4">
<label for="t13_4" class="task-label">
<strong>TODO 4:</strong> <code>laden_aus_json(dateipfad)</code>: Liest mit <code>json.load</code> die Datei ein und stellt die <code>spieler_liste</code> mit <code>Spieler.from_dict(...)</code> wieder her.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t13_5">
<label for="t13_5" class="task-label">
<strong>TODO 5:</strong> <code>exportiere_als_csv(dateipfad)</code> und <code>importiere_aus_csv(dateipfad)</code> mit <code>csv.DictWriter</code> und <code>csv.DictReader</code>.
</label>
</li>
</ul>
<h3>💡 Lösungshinweise (Aufklappen bei Bedarf)</h3>
<details>
<summary>🔍 Tipp zum JSON-Speichern</summary>
<div class="details-content">
<div class="code-container">
<pre><span class="var">daten</span> = {
<span class="str">"titel"</span>: <span class="var">self</span>.<span class="attr">titel</span>,
<span class="str">"spieler"</span>: [<span class="var">s</span>.<span class="fn">to_dict</span>() <span class="kw">for</span> <span class="var">s</span> <span class="kw">in</span> <span class="var">self</span>.<span class="attr">spieler_liste</span>]
}
<span class="kw">with</span> <span class="fn">open</span>(<span class="var">dateipfad</span>, <span class="str">"w"</span>, <span class="var">encoding</span>=<span class="str">"utf-8"</span>) <span class="kw">as</span> <span class="var">f</span>:
<span class="var">json</span>.<span class="fn">dump</span>(<span class="var">daten</span>, <span class="var">f</span>, <span class="var">indent</span>=<span class="num">4</span>, <span class="var">ensure_ascii</span>=<span class="kw">False</span>)</pre>
</div>
</div>
</details>
<details>
<summary>🔍 Tipp zum CSV-Export</summary>
<div class="details-content">
<div class="code-container">
<pre><span class="var">feldnamen</span> = [<span class="str">"name"</span>, <span class="str">"level"</span>, <span class="str">"punkte"</span>, <span class="str">"inventar_anzahl"</span>]
<span class="kw">with</span> <span class="fn">open</span>(<span class="var">dateipfad</span>, <span class="str">"w"</span>, <span class="var">newline</span>=<span class="str">""</span>, <span class="var">encoding</span>=<span class="str">"utf-8"</span>) <span class="kw">as</span> <span class="var">f</span>:
<span class="var">writer</span> = <span class="var">csv</span>.<span class="fn">DictWriter</span>(<span class="var">f</span>, <span class="var">fieldnames</span>=<span class="var">feldnamen</span>)
<span class="var">writer</span>.<span class="fn">writeheader</span>()
<span class="kw">for</span> <span class="var">s</span> <span class="kw">in</span> <span class="var">self</span>.<span class="attr">spieler_liste</span>:
<span class="var">writer</span>.<span class="fn">writerow</span>({
<span class="str">"name"</span>: <span class="var">s</span>.<span class="attr">name</span>,
<span class="str">"level"</span>: <span class="var">s</span>.<span class="attr">level</span>,
<span class="str">"punkte"</span>: <span class="var">s</span>.<span class="attr">punkte</span>,
<span class="str">"inventar_anzahl"</span>: <span class="fn">len</span>(<span class="var">s</span>.<span class="attr">inventar</span>)
})</pre>
</div>
</div>
</details>
</section>
<!-- 5. Testen & Navigation -->
<section class="card">
<h2>5. 🧪 Lösung überprüfen</h2>
<p>Führe im Terminal im Ordner <code>13_persistenz_json_und_csv</code> folgenden Befehl aus:</p>
<div class="code-container">
<div class="code-header">
<span>Terminal</span>
</div>
<pre><code>python3 test_aufgabe.py</code></pre>
</div>
<div class="footer-nav">
<a href="../12_exceptions_und_fehlerbehandlung/index.html" class="btn btn-secondary">&larr; Zurück zu Kapitel 12</a>
<a href="../14_gui_mit_tkinter/index.html" class="btn">Weiter zu Kapitel 14 &rarr;</a>
</div>
</section>
</main>
</body>
</html>