251 lines
15 KiB
HTML
251 lines
15 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 13: Datei-Persistenz (JSON & 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>›</span>
|
||
<a href="../12_exceptions_und_fehlerbehandlung/index.html">Kapitel 12</a>
|
||
<span>›</span>
|
||
<span style="color: white; font-weight: 600;">Kapitel 13</span>
|
||
</nav>
|
||
<div class="header-badge">Kapitel 13 • Schulabgleich 14.0</div>
|
||
<h1>Datei-Persistenz: JSON & 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 & 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">← Zurück zu Kapitel 12</a>
|
||
<a href="../14_gui_mit_tkinter/index.html" class="btn">Weiter zu Kapitel 14 →</a>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
|
||
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
|
||
<script src="../assets/gamification_data.js"></script>
|
||
<script src="../assets/gamification.js"></script>
|
||
</body>
|
||
</html>
|