Erstelle Lehrpfad 4: Python Professional & Data Engineering (Module 17-22: Regex, Functional, Generatoren, Dataclasses, SQLite, REST-APIs)

This commit is contained in:
Alex Mueller 2026-08-29 23:33:04 +02:00
parent b240ed9a6b
commit 108da4c12a
37 changed files with 7004 additions and 18 deletions

View file

@ -1,14 +1,17 @@
{
"spieler_name": "Python-Entwicklerin",
"xp": 0,
"level": 1,
"titel": "Code-Küken 🐣",
"rang": "Bronze I",
"geloeste_kapitel": [],
"bestandene_tests": 0,
"xp": 360,
"level": 2,
"titel": "Code-Detektivin 🔍",
"rang": "Bronze II",
"geloeste_kapitel": [
"lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools",
"lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints"
],
"bestandene_tests": 16,
"freigeschaltete_badges": [],
"streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29",
"historie": [],
"zuletzt_aktualisiert": "2026-08-29T21:21:16.433076"
"zuletzt_aktualisiert": "2026-08-29T23:32:59.778016"
}

View file

@ -42,6 +42,14 @@ Es gleicht die gesamte Übungsreihe (Grundlagen & Fortgeschrittene Objektori
├── 14: Desktop-GUIs mit Tkinter & MVC-Architektur (Zähler & Rechner) (Lehrer 27.0)
├── 15: Parameter (*args, **kwargs) & Eigene Container (Gaming-Inventar) (Lehrer 29.0 & 30.0)
└── 16: Master-Abschlussprojekt: Professionelle Desktop-App (PetCare) (Meisterstück & MVC)
[ 🛣️ LEHRPFAD 4: PROFESSIONAL DATA ENGINEERING & WEB-APIS ]
├── 17: Reguläre Ausdrücke & Text-Mining (re, Muster, Quantifizierer, Gruppen) (Text & Data Mining)
├── 18: Funktionale Programmierung (lambda, zip, enumerate, map/filter, sort) (Pythonic Code)
├── 19: Generatoren, Iteratoren & itertools (yield, Lazy Evaluation, Streams) (Memory Efficiency)
├── 20: Dataclasses, Type Hints & Decorators (@dataclass, typing, Wrappers) (Moderne Architektur)
├── 21: Relationale Datenbanken & SQLite (CRUD, sqlite3, SQL-Injection Schutz) (SQL & Persistenz)
└── 22: Web-APIs, REST & JSON-Feeds (REST, JSON-Parsing, Statuscodes, Fehlerbehandlung) (Web & Daten)
```
---
@ -79,4 +87,10 @@ Es gleicht die gesamte Übungsreihe (Grundlagen & Fortgeschrittene Objektori
| **14** | **Tkinter GUI** | Echte Desktop-Apps, Widgets, `grid()`, MVC-Muster | Interaktive Zähler- & Rechner-App |
| **15** | **Parameter & Container** | `*args`, `**kwargs`, Unpacking & Container-Dunder (`__getitem__`, `__setitem__`, `__delitem__`, `__contains__`, `__iter__`) | Gaming-Rucksack & Inventar |
| **16** | **Master-Abschlussprojekt** | Vollständiges Desktop-Meisterstück: MVC-Architektur, Vererbung & Polymorphie, JSON-Savegames, CSV-Export, Exceptions & Tkinter-GUI | PetCare- & Tierheim-Manager Pro |
| **17** | **Reguläre Ausdrücke (re)** | Metazeichen (`\d`, `\w`, `\s`), Quantifizierer, Gruppen, `re.search`, `re.findall`, `re.sub` | E-Mail-Check, Telefon-Finder & IBAN-Maskierung |
| **18** | **Funktionale Programmierung** | `enumerate()`, `zip()`, `lambda`, `map()`, `filter()`, `sorted(..., key=...)`, `all()`, `any()` | Fließband-Datenpipeline, Kataloge & Alterssortierung |
| **19** | **Generatoren & itertools** | `yield`, Lazy Evaluation, `iter()`, `next()`, `itertools` (`chain`, `cycle`, `permutations`) | Riesige Datenströme & Speicher-Benchmarks |
| **20** | **Dataclasses & Type Hints** | `@dataclass`, `field(default_factory=...)`, Eigene Decorators, `Union`, `Callable` | Online-Shop-Architektur & Funktions-Tracker |
| **21** | **Datenbanken & SQLite** | `sqlite3`, Tabellen, CRUD, Parametrisierte Abfragen (`?`), Transaktionen | KundenDatenbank mit Kontoständen |
| **22** | **Web-APIs & JSON-Feeds** | REST-Architektur, `json.loads/dumps`, Statuscodes, defensive Fehlerbehandlung | Wetter- & Finanz-Feeds, Ticker-Generator |

View file

@ -33,9 +33,13 @@
titel = "Fortgeschrittenes OOP, Softwarequalität & Desktop-GUIs";
moduleDesc = "10 Module: Speicher-Referenzen, Dunder-Methoden, TDD Unit Testing, Vererbung, Polymorphie, Exceptions, JSON-Persistenz, Tkinter GUIs, Container & Masterprojekt";
trackKey = "lehrpfad_3";
} else if (lehrpfadNummer === 4 || lehrpfadNummer === "lehrpfad_4") {
titel = "Python Professional & Data Engineering";
moduleDesc = "6 Master-Module: Reguläre Ausdrücke (re), Functional Programming (lambda, zip, map), Generatoren & itertools, Dataclasses & Type Hints, Relationale DBs mit SQLite, REST-APIs & JSON-Feeds";
trackKey = "lehrpfad_4";
} else if (lehrpfadNummer === "master") {
titel = "Master of Python Software Engineering";
moduleDesc = "Vollständiger Abschluss aller 3 Lehrpfade (27 Module, 188 bestandene automatisierte Unittests, Model-View-Controller Desktop-App)";
titel = "Grandmaster of Python Software Engineering";
moduleDesc = "Vollständiger Abschluss aller 4 Lehrpfade (33 Module, über 220 bestandene automatisierte Unittests, SQLite-Persistenz, REST-APIs & MVC-Desktop-App)";
trackKey = "master";
}

View file

@ -1,17 +1,20 @@
// Automatisch generierter lokaler Gamification-Spielstand
window.GAMIFICATION_DATA = {
"spieler_name": "Python-Entwicklerin",
"xp": 0,
"level": 1,
"titel": "Code-Küken 🐣",
"rang": "Bronze I",
"geloeste_kapitel": [],
"bestandene_tests": 0,
"xp": 360,
"level": 2,
"titel": "Code-Detektivin 🔍",
"rang": "Bronze II",
"geloeste_kapitel": [
"lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools",
"lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints"
],
"bestandene_tests": 16,
"freigeschaltete_badges": [],
"streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29",
"historie": [],
"zuletzt_aktualisiert": "2026-08-29T21:21:16.433076"
"zuletzt_aktualisiert": "2026-08-29T23:32:59.778016"
};
window.GAMIFICATION_BADGES_KATALOG = {
"rechenkonegin": {

View file

@ -167,6 +167,32 @@
</a>
</div>
</div>
<!-- LEHRPFAD 4 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>⚡ 4. Data Engineering</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">17–20</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="./lehrpfad_4_professional_data_engineering/17_regulaere_ausdruecke_re/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">17. Reguläre Ausdrücke (re)</span>
</a>
<a href="./lehrpfad_4_professional_data_engineering/18_funktionale_programmierung_lambda/index.html" class="sidebar-link">
<span class="sidebar-link-icon">⚡</span>
<span class="sidebar-link-text">18. Funktionale Programmierung</span>
</a>
<a href="./lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌊</span>
<span class="sidebar-link-text">19. Generatoren & itertools</span>
</a>
<a href="./lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏷️</span>
<span class="sidebar-link-text">20. Dataclasses & Types</span>
</a>
</div>
</div>
</div>
<div class="sidebar-footer">
@ -829,6 +855,88 @@
</div>
</section>
<!-- LEHRPFAD 4 -->
<section>
<div class="section-header">
<div>
<h2 id="lehrpfad-4">⚡ Lehrpfad 4: Professional Data Engineering</h2>
<div class="section-desc">Reguläre Ausdrücke, funktionale Programmierung, Generatoren (yield/itertools) und moderne Dataclasses (17–20).</div>
</div>
<span class="track-badge" style="background: #0ea5e9; color: white;">4 Module</span>
</div>
<div class="roadmap-grid">
<div class="roadmap-card" style="border-left: 4px solid #0ea5e9;">
<div>
<span class="roadmap-number" style="background: #0ea5e918; color: #0ea5e9;">Kapitel 17</span>
<div class="roadmap-title">🔍 Reguläre Ausdrücke (re)</div>
<div class="roadmap-desc">Text-Mining & Validierung: E-Mails, Telefonnummern, Hashtags, IBAN-Maskierung & ISO-Datumsformate.</div>
</div>
<div class="card-actions">
<a href="workspace.html?track=lehrpfad_4_professional_data_engineering&chapter=17_regulaere_ausdruecke_re" class="card-primary-btn" style="background: #0ea5e9;">💻 In Web-IDE öffnen &rarr;</a>
<a href="lehrpfad_4_professional_data_engineering/17_regulaere_ausdruecke_re/index.html" style="text-align: center; font-size: 0.8rem; color: var(--text-muted); text-decoration: none; font-weight: 600;">📖 Kapitel-Guide lesen</a>
<div class="card-sublinks">
<a href="lehrpfad_4_professional_data_engineering/17_regulaere_ausdruecke_re/aufgabe.py" download>📄 aufgabe.py</a>
<a href="lehrpfad_4_professional_data_engineering/17_regulaere_ausdruecke_re/test_aufgabe.py" download>🧪 tests</a>
<a href="lehrpfad_4_professional_data_engineering/17_regulaere_ausdruecke_re/musterloesung.py" download>💡 lösung</a>
</div>
</div>
</div>
<div class="roadmap-card" style="border-left: 4px solid #0ea5e9;">
<div>
<span class="roadmap-number" style="background: #0ea5e918; color: #0ea5e9;">Kapitel 18</span>
<div class="roadmap-title">⚡ Funktionale Programmierung</div>
<div class="roadmap-desc">Pythonic Code: Fließband-Datenpipelines mit enumerate, zip, lambda, map, filter, custom sort, any & all.</div>
</div>
<div class="card-actions">
<a href="workspace.html?track=lehrpfad_4_professional_data_engineering&chapter=18_funktionale_programmierung_lambda" class="card-primary-btn" style="background: #0ea5e9;">💻 In Web-IDE öffnen &rarr;</a>
<a href="lehrpfad_4_professional_data_engineering/18_funktionale_programmierung_lambda/index.html" style="text-align: center; font-size: 0.8rem; color: var(--text-muted); text-decoration: none; font-weight: 600;">📖 Kapitel-Guide lesen</a>
<div class="card-sublinks">
<a href="lehrpfad_4_professional_data_engineering/18_funktionale_programmierung_lambda/aufgabe.py" download>📄 aufgabe.py</a>
<a href="lehrpfad_4_professional_data_engineering/18_funktionale_programmierung_lambda/test_aufgabe.py" download>🧪 tests</a>
<a href="lehrpfad_4_professional_data_engineering/18_funktionale_programmierung_lambda/musterloesung.py" download>💡 lösung</a>
</div>
</div>
</div>
<div class="roadmap-card" style="border-left: 4px solid #0ea5e9;">
<div>
<span class="roadmap-number" style="background: #0ea5e918; color: #0ea5e9;">Kapitel 19</span>
<div class="roadmap-title">🌊 Generatoren & itertools</div>
<div class="roadmap-desc">Memory Efficiency: Riesige Datenmengen streamen mit <code>yield</code>, Lazy Evaluation und <code>itertools</code>.</div>
</div>
<div class="card-actions">
<a href="workspace.html?track=lehrpfad_4_professional_data_engineering&chapter=19_generatoren_und_itertools" class="card-primary-btn" style="background: #0ea5e9;">💻 In Web-IDE öffnen &rarr;</a>
<a href="lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools/index.html" style="text-align: center; font-size: 0.8rem; color: var(--text-muted); text-decoration: none; font-weight: 600;">📖 Kapitel-Guide lesen</a>
<div class="card-sublinks">
<a href="lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools/aufgabe.py" download>📄 aufgabe.py</a>
<a href="lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools/test_aufgabe.py" download>🧪 tests</a>
<a href="lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools/musterloesung.py" download>💡 lösung</a>
</div>
</div>
</div>
<div class="roadmap-card" style="border-left: 4px solid #0ea5e9;">
<div>
<span class="roadmap-number" style="background: #0ea5e918; color: #0ea5e9;">Kapitel 20</span>
<div class="roadmap-title">🏷️ Dataclasses & Type Hints</div>
<div class="roadmap-desc">Moderne Architektur: Typsichere Datenmodelle mit <code>@dataclass</code>, Decorators und fortgeschrittenen Type Hints.</div>
</div>
<div class="card-actions">
<a href="workspace.html?track=lehrpfad_4_professional_data_engineering&chapter=20_dataclasses_und_typehints" class="card-primary-btn" style="background: #0ea5e9;">💻 In Web-IDE öffnen &rarr;</a>
<a href="lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints/index.html" style="text-align: center; font-size: 0.8rem; color: var(--text-muted); text-decoration: none; font-weight: 600;">📖 Kapitel-Guide lesen</a>
<div class="card-sublinks">
<a href="lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints/aufgabe.py" download>📄 aufgabe.py</a>
<a href="lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints/test_aufgabe.py" download>🧪 tests</a>
<a href="lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints/musterloesung.py" download>💡 lösung</a>
</div>
</div>
</div>
</div>
</section>
<!-- 🎮 Gamification Dashboard -->
<section class="hud-card" id="gamification-hud">
<div class="hud-header">
@ -934,7 +1042,8 @@ print(mein_auto.hupen())
<button type="button" class="btn" onclick="if(window.openCertificate) window.openCertificate('lehrpfad_1');">📜 Zertifikat Lehrpfad 1</button>
<button type="button" class="btn" onclick="if(window.openCertificate) window.openCertificate('lehrpfad_2');">📜 Zertifikat Lehrpfad 2</button>
<button type="button" class="btn" onclick="if(window.openCertificate) window.openCertificate('lehrpfad_3');">📜 Zertifikat Lehrpfad 3</button>
<button type="button" class="btn" onclick="if(window.openCertificate) window.openCertificate('master');">👑 Master-Zertifikat</button>
<button type="button" class="btn" onclick="if(window.openCertificate) window.openCertificate('lehrpfad_4');">📜 Zertifikat Lehrpfad 4</button>
<button type="button" class="btn" onclick="if(window.openCertificate) window.openCertificate('master');">👑 Grandmaster-Zertifikat</button>
</div>
</section>

View file

@ -0,0 +1,114 @@
# Kapitel 17: Reguläre Ausdrücke (re) – Text-Mining & Validierung 🔍🕵️
Willkommen zu **Kapitel 17** und dem Start von **Lehrpfad 4 (Professional Data Engineering)**!
In diesem Modul lernst du das Schweizer Taschenmesser der Textverarbeitung kennen: **Reguläre Ausdrücke (Regular Expressions / Regex)** mit Pythons eingebautem `re`-Modul.
---
## 🧭 Didaktischer Hintergrund & Die Detektiv-Analogie
In der Datenanalyse und beim Data Engineering sind 80% aller Rohdaten unstrukturiert: Logdateien, E-Mails, Rechnungs-PDFs, Social-Media-Feeds oder Kundeneingaben. Einfache String-Methoden (`.find()`, `.split()`) stoßen hier schnell an ihre Grenzen.
> 🕵️ **Die Detektiv-Analogie:**
> Ein regulärer Ausdruck ist wie ein **Phantombild für den Muster-Detektiv**.
> Anstatt nach einem festen Text zu suchen (*"Finde exakt 'Peter'"*), suchst du nach einer **Struktur**:
> *„Suche nach 2 Buchstaben, gefolgt von 2 Ziffern, gefolgt von vielen Zahlen – das ist eine IBAN!“*
---
## 🧰 1. Metazeichen: Die Bausteine von Regex
| Metazeichen | Bedeutung | Beispiel | Treffer |
| :--- | :--- | :--- | :--- |
| `\d` | **Digit**: Beliebige Ziffer (0-9) | `\d{3}` | `123`, `999` |
| `\D` | **Keine Ziffer**: Alles außer 0-9 | `\D+` | `abc`, `Hallo` |
| `\w` | **Word character**: Buchstabe, Ziffer, Unterstrich | `\w+` | `user_42`, `Python` |
| `\W` | **Kein Word character**: Leerzeichen, Satzzeichen | `\W` | `!`, `@`, ` ` |
| `\s` | **Whitespace**: Leerzeichen, Tab, Zeilenumbruch | `\s+` | ` `, `\t`, `\n` |
| `.` | **Beliebiges Zeichen** (außer Newline) | `a.c` | `abc`, `a-c`, `a9c` |
| `^` | **Zeilenanfang / Textanfang** | `^Start` | `Starten...` |
| `$` | **Zeilenende / Textende** | `Ende$` | `...am Ende` |
| `\b` | **Wortgrenze**: Übergang von `\w` zu `\W` | `\bTag\b` | `Tag` (nicht `Tagebuch`) |
> ⚠️ **Wichtig – Immer Raw-Strings `r"..."` nutzen:**
> Verwende für Regex-Muster in Python immer das Präfix `r"..."` (z.B. `r"\d+\s\w+"`), damit Python Backslashes nicht als Escape-Sequenzen wie `\n` oder `\t` interpretiert.
---
## 🔢 2. Quantifizierer: Wie oft kommt ein Zeichen vor?
| Quantifizierer | Häufigkeit | Bedeutung | Beispiel |
| :--- | :--- | :--- | :--- |
| `+` | $\ge 1$ | Mindestens einmal (1 bis unendlich) | `\d+` $\rightarrow$ `5`, `12345` |
| `*` | $\ge 0$ | Null oder mehrmals (optional beliebig oft) | `ab*c` $\rightarrow$ `ac`, `abc`, `abbc` |
| `?` | $0$ oder $1$ | Optional (höchstens einmal) | `https?` $\rightarrow$ `http`, `https` |
| `{n}` | Exakt $n$ | Genau $n$ Wiederholungen | `\d{4}` $\rightarrow$ `2026` |
| `{min,max}` | $min$ bis $max$ | Bereich von Wiederholungen | `\d{2,4}` $\rightarrow$ `12`, `123`, `1234` |
---
## 🎯 3. Zeichenklassen & Gruppen
### A) Benutzerdefinierte Zeichenklassen `[...]`
- `[aeiou]` $\rightarrow$ Findet einen Vokal
- `[a-zA-Z0-9]` $\rightarrow$ Alphanumerische Zeichen
- `[^0-9]` $\rightarrow$ Negation: Alles **außer** Ziffern
### B) Gruppen `(...)` & Benannte Gruppen `(?P<name>...)`
Mit runden Klammern schneidest du Teiltreffer gezielt heraus:
```python
import re
muster = r"^(?P<jahr>\d{4})-(?P<monat>\d{2})-(?P<tag>\d{2})$"
match = re.match(muster, "2026-08-29")
if match:
print(match.group("jahr")) # -> '2026'
print(match.groupdict()) # -> {'jahr': '2026', 'monat': '08', 'tag': '29'}
```
---
## ⚙️ 4. Die wichtigsten Methoden des `re`-Moduls
| Methode | Wann verwenden? | Rückgabetyp |
| :--- | :--- | :--- |
| `re.search(pattern, text)` | Erstes Vorkommen irgendwo im Text suchen | `re.Match` oder `None` |
| `re.fullmatch(pattern, text)` | Ganzen String von Anfang bis Ende prüfen (Validierung) | `re.Match` oder `None` |
| `re.findall(pattern, text)` | Alle Treffer im Text als Liste sammeln | `list[str]` oder `list[tuple]` |
| `re.finditer(pattern, text)` | Alle Treffer als Iterator mit Positionen durchlaufen | `Iterator[re.Match]` |
| `re.sub(pattern, repl, text)` | Suchen & Ersetzen (z.B. Maskierung vertraulicher Daten) | `str` |
### Praxisbeispiel: Datenbereinigung mit `re.sub`
```python
# IBANs maskieren
text = "Konto: DE89370400440532013000"
maskiert = re.sub(
r"\b([A-Z]{2}\d{2})([A-Z0-9]{8,30})\b",
lambda m: m.group(1) + ("*" * len(m.group(2))),
text
)
print(maskiert) # -> Konto: DE89******************
```
---
## 📁 Die Dateien in diesem Modul
- **`index.html`**: Interaktive Lernseite mit Cheat-Sheet, Live-Beispielen, Checkliste und Lösungstipps.
- **`aufgabe.py`**: Dein Arbeitsblatt mit Type Hints und TODOs für E-Mail-Check, Telefon-Finder, Hashtags, IBAN-Maskierung und ISO-Datum-Parsing.
- **`test_aufgabe.py`**: Automatische Unittest-Suite mit Grenzfall-Tests.
- **`musterloesung.py`**: Vollständig ausprogrammierte Referenzlösung.
---
## 🚀 Schnellstart
```bash
# 1. Bearbeite aufgabe.py
# 2. Führe die automatischen Unittests aus:
python3 test_aufgabe.py
# 3. Oder starte aufgabe.py direkt im Terminal:
python3 aufgabe.py
```

View file

@ -0,0 +1,222 @@
r"""
Kapitel 17: Reguläre Ausdrücke (re) – Text-Mining & Validierung 🔍🕵️
===================================================================
Aufgabe: Werde zum Muster-Detektiv! Nutze Pythons eingebautes `re`-Modul,
um strukturierte Daten aus unstrukturiertem Text zu filtern, Formate zu
validieren und vertrauliche Informationen zu maskieren.
Themen:
1. Metazeichen: \d (Ziffer), \w (Wortzeichen), \s (Whitespace), . (Beliebig), ^, $
2. Quantifizierer: +, *, ?, {min,max}
3. Gruppen & Benannte Gruppen: (group) und (?P<name>...)
4. Methoden: re.search(), re.match(), re.findall(), re.sub(), re.fullmatch()
"""
import re
from typing import List, Dict, Optional, Any
# ==============================================================================
# TEIL 1: VALIDIERUNG VON E-MAIL-ADRESSEN
# ==============================================================================
def ist_gueltige_email(email: str) -> bool:
r"""
TODO 1: Prüft, ob ein übergebener String eine gültige E-Mail-Adresse ist.
Regeln für eine gültige E-Mail:
- Benutzername: Mindestens 1 Zeichen aus Buchstaben (a-z, A-Z), Ziffern (0-9),
Punkten (.), Unterstrichen (_), Plus (+) oder Bindestrichen (-).
- Ein einzelnes '@'-Trennzeichen.
- Domain-Name: Mindestens 1 Zeichen aus Buchstaben, Ziffern oder Bindestrichen.
- Domain-Endung (TLD): Ein Punkt gefolgt von mindestens 2 Buchstaben (z.B. .de, .com, .at).
- Es dürfen keine führenden/nachfolgenden Leerzeichen oder Leerzeichen innerhalb sein.
Beispiele:
ist_gueltige_email("max.mustermann@schule.at") -> True
ist_gueltige_email("dev_123+tag@gmail.com") -> True
ist_gueltige_email("kein_at_zeichen.de") -> False
ist_gueltige_email("@domain.com") -> False
ist_gueltige_email("user@domain") -> False
ist_gueltige_email("user@domain.c") -> False
Tipp:
Verwende ein Regex-Muster wie: r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$"
und teste mit re.fullmatch(pattern, email) is not None.
"""
# 🎯 TEILZIEL 1: Implementiere die E-Mail-Validierung
pass
# ==============================================================================
# TEIL 2: EXTRAKTION VON TELEFONNUMMERN
# ==============================================================================
def finde_telefonnummern(text: str) -> List[str]:
r"""
TODO 2: Findet alle Telefonnummern in einem Text und gibt sie als Liste zurück.
Unterstützte Formate:
- Deutsche/Österreichische Standardnummern: "0171-1234567", "0171 1234567"
- Mit Ländervorwahl: "+49 171 1234567", "+49-171-1234567", "+43 664 1234567"
- Mit Vorwahlklammern: "(089) 123456", "(030) 98765432"
- Mit Schrägstrich: "089/123456", "030/98765432"
Beispiele:
text = "Ruf mich an unter 0171-1234567 oder im Büro unter +49 171 1234567. Notfall: (089) 123456."
finde_telefonnummern(text)
-> ["0171-1234567", "+49 171 1234567", "(089) 123456"]
Tipp:
Nutze re.findall(). Ein typisches Suchmuster berücksichtigt:
- Ländervorwahl oder Vorwahl mit Klammern/Null: (?:\+\d{1,3}[ -]\d{2,5}|\(\d{2,5}\)|0\d{1,5})
- Optionales Trennzeichen [- /]?
- Anschlussnummer: \d{3,10}\b
"""
# 🎯 TEILZIEL 2: Extrahiere alle Telefonnummern
pass
# ==============================================================================
# TEIL 3: SOCIAL MEDIA HASHTAGS EXTRAHIEREN
# ==============================================================================
def extrahiere_hashtags(text: str) -> List[str]:
r"""
TODO 3: Extrahiere alle Social-Media-Hashtags aus einem Text.
Regeln:
- Ein Hashtag beginnt mit dem Raute-Zeichen '#'.
- Darauf folgen 1 oder mehr Wortzeichen (Buchstaben, Ziffern, Unterstriche).
- Deutsche Umlaute (ä, ö, ü, ß) sollen ebenfalls unterstützt werden.
- Ein einzelnes '#' ohne nachfolgende Wortzeichen ist KEIN Hashtag.
Beispiele:
extrahiere_hashtags("Lerne #python und #data_engineering! #2026 #süß")
-> ["#python", "#data_engineering", "#2026", "#süß"]
extrahiere_hashtags("Nur ein normales Zeichen # und Text")
-> []
Tipp:
Nutze re.findall() mit dem Muster r"#[a-zA-Z0-9_äöüÄÖÜß]+" oder r"#\w+".
"""
# 🎯 TEILZIEL 3: Extrahiere Hashtags
pass
# ==============================================================================
# TEIL 4: DATENSCHUTZ & MASKIERUNG (IBAN)
# ==============================================================================
def maskiere_iban(text: str) -> str:
r"""
TODO 4: Maskiert alle IBANs in einem Text aus Datenschutzgründen.
Regeln:
- Eine IBAN besteht aus einem 2-stelligen Ländercode (z.B. DE, AT, CH),
gefolgt von 2 Prüfziffern und weiteren 8 bis 30 alphanumerischen Zeichen.
- Die ersten 4 Zeichen (z.B. 'DE89') bleiben sichtbar.
- Alle darauffolgenden Zeichen der IBAN werden durch '*' ersetzt (exakt selbe Anzahl an Sternchen!).
- Der restliche Text um die IBAN herum bleibt unverändert.
Beispiele:
maskiere_iban("Bitte überweisen auf DE89370400440532013000 danke!")
-> "Bitte überweisen auf DE89****************** danke!"
maskiere_iban("AT611904300234573201 und DE1234567890")
-> "AT61**************** und DE12********"
Tipp:
Nutze re.sub() mit einer Ersetzungsfunktion:
pattern = r"\b([A-Z]{2}\d{2})([A-Z0-9]{8,30})\b"
re.sub(pattern, lambda m: m.group(1) + ("*" * len(m.group(2))), text)
"""
# 🎯 TEILZIEL 4: Maskiere gefundene IBANs mit re.sub()
pass
# ==============================================================================
# TEIL 5: BENANNTE GRUPPEN (ISO-DATUM PARSEN)
# ==============================================================================
def parse_datum_iso(datum_str: str) -> Dict[str, str]:
r"""
TODO 5: Parst ein Datum im ISO-Format (YYYY-MM-DD) mithilfe benannter Gruppen
(?P<name>...) und gibt ein Dictionary zurück.
Format:
- jahr: 4 Ziffern (z.B. '2026')
- monat: 2 Ziffern (z.B. '08')
- tag: 2 Ziffern (z.B. '29')
Rückgabe:
Dict[str, str]: {'jahr': '2026', 'monat': '08', 'tag': '29'}
Exceptions:
ValueError: Falls das Format nicht exakt dem ISO-Muster entspricht.
Beispiele:
parse_datum_iso("2026-08-29") -> {'jahr': '2026', 'monat': '08', 'tag': '29'}
parse_datum_iso("29.08.2026") -> löst ValueError aus!
parse_datum_iso("2026/08/29") -> löst ValueError aus!
Tipp:
Verwende das Muster r"^(?P<jahr>\d{4})-(?P<monat>\d{2})-(?P<tag>\d{2})$".
Rufe match = re.fullmatch(pattern, datum_str.strip()) auf.
Mit match.groupdict() erhältst du direkt das fertige Dictionary!
"""
# 🎯 TEILZIEL 5: Parse ISO-Datum mit benannten Gruppen
pass
# ==============================================================================
# TEIL 6: BONUS: LOGFILE-PARSER
# ==============================================================================
def parse_log_zeile(log_zeile: str) -> Dict[str, str]:
r"""
TODO 6 (Bonus): Parst eine Server-Logzeile und extrahiert Metadaten.
Format einer Logzeile:
"[YYYY-MM-DD HH:MM:SS] [LEVEL] [SERVICE] NACHRICHT"
Beispiel:
"[2026-08-29 14:32:00] [ERROR] [auth_service] Login fehlgeschlagen für user 'admin'"
Rückgabe:
Dict mit den Schlüsseln:
- "timestamp": z.B. "2026-08-29 14:32:00"
- "level": z.B. "ERROR"
- "service": z.B. "auth_service"
- "message": z.B. "Login fehlgeschlagen für user 'admin'"
Exceptions:
ValueError: Wenn die Logzeile nicht dem erwarteten Format entspricht.
"""
# 🎯 TEILZIEL 6: Parse Server-Logzeile mit Gruppen
pass
if __name__ == "__main__":
print("=" * 60)
print("🔍 KAPITEL 17: REGEX-DETEKTIV TESTLAUF")
print("=" * 60)
# 1. E-Mail Test
test_mail = "tutor@antigravity-academy.de"
print(f"E-Mail '{test_mail}' gültig?:", ist_gueltige_email(test_mail))
# 2. Telefonnummern
text = "Zentrale: +49 89 1234567, Mobil: 0171-9876543, Büro: (030) 555444"
print("Gefundene Telefonnummern:", finde_telefonnummern(text))
# 3. Hashtags
tweet = "Python 3.12 ist genial! #python #data_science #AI2026 #süß"
print("Gefundene Hashtags:", extrahiere_hashtags(tweet))
# 4. IBAN-Maskierung
vertraulich = "Konto A: DE89370400440532013000, Konto B: AT611904300234573201"
print("Maskierter Text:", maskiere_iban(vertraulich))
# 5. ISO-Datum
datum = "2026-08-29"
print("Geparstes Datum:", parse_datum_iso(datum))

View file

@ -0,0 +1,509 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapitel 17: Reguläre Ausdrücke (re) – Text-Mining & Validierung</title>
<link rel="stylesheet" href="../../assets/style.css">
</head>
<body>
<div class="app-layout">
<aside class="app-sidebar">
<div class="sidebar-header">
<a href="../../index.html" class="sidebar-brand-link">
<div class="sidebar-logo">🐍</div>
<div>
<div class="sidebar-title">Python Portal</div>
<div class="sidebar-subtitle">Lernplattform</div>
</div>
</a>
</div>
<div class="sidebar-quick-nav">
<a href="../../index.html" class="sidebar-link">🏠 Startseite</a>
<a href="../../index.html#tutorial" class="sidebar-link">📖 Tutorial &amp; Workflow</a>
<a href="../../index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a>
</div>
<div class="sidebar-nav">
<!-- LEHRPFAD 1 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🌱 1. Grundlagen</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">G01–G10</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_1_grundlagen/01_erste_schritte_taschenrechner/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧮</span>
<span class="sidebar-link-text">G01. Taschenrechner</span>
</a>
<a href="../../lehrpfad_1_grundlagen/02_variablen_und_datentypen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📦</span>
<span class="sidebar-link-text">G02. Variablen & Typen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/03_ein_und_ausgabe/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💬</span>
<span class="sidebar-link-text">G03. Ein- & Ausgabe</span>
</a>
<a href="../../lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚦</span>
<span class="sidebar-link-text">G04. Bedingungen (if)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎡</span>
<span class="sidebar-link-text">G05. Schleifen (for/while)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/06_funktionen_und_module/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🪄</span>
<span class="sidebar-link-text">G06. Funktionen & Module</span>
</a>
<a href="../../lehrpfad_1_grundlagen/07_listen_und_sequenzen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📋</span>
<span class="sidebar-link-text">G07. Listen & Sequenzen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/08_textverarbeitung_und_strings/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔤</span>
<span class="sidebar-link-text">G08. String-Methoden</span>
</a>
<a href="../../lehrpfad_1_grundlagen/09_dictionaries_und_sets/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗃️</span>
<span class="sidebar-link-text">G09. Dicts & Sets</span>
</a>
<a href="../../lehrpfad_1_grundlagen/10_comprehensions_datum_algorithmen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚀</span>
<span class="sidebar-link-text">G10. Comprehensions</span>
</a>
</div>
</div>
<!-- LEHRPFAD 2 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🏗️ 2. OOP Einstieg</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">00–06</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">00. Fehlersuche & Debug</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/01_einstieg_klassen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏗️</span>
<span class="sidebar-link-text">01. Einstieg in Klassen</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/02_init_und_self/index.html" class="sidebar-link">
<span class="sidebar-link-icon">⚙️</span>
<span class="sidebar-link-text">02. Konstruktor & self</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/03_methoden_und_verhalten/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏎️</span>
<span class="sidebar-link-text">03. Methoden & Verhalten</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04_str_und_darstellung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧾</span>
<span class="sidebar-link-text">04. __str__ Darstellung</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04b_umstieg_vscode/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💻</span>
<span class="sidebar-link-text">04b. VS Code Setup</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04c_git_und_versionskontrolle/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌿</span>
<span class="sidebar-link-text">04c. Git-Versionskontrolle</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/05_objekte_kombinieren/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎵</span>
<span class="sidebar-link-text">05. Objekte kombinieren</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🥚</span>
<span class="sidebar-link-text">06. Projekt: Tamagotchi</span>
</a>
</div>
</div>
<!-- LEHRPFAD 3 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🚀 3. Fortgeschritten</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">07–16</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_3_fortgeschrittenes_oop/07_referenzen_und_speicher/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧠</span>
<span class="sidebar-link-text">07. Referenzen & Speicher</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/08_operator_overloading_dunder/index.html" class="sidebar-link">
<span class="sidebar-link-icon">➕</span>
<span class="sidebar-link-text">08. Operator Overloading</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧪</span>
<span class="sidebar-link-text">09. Unit Tests & TDD</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧬</span>
<span class="sidebar-link-text">10. Vererbung & super()</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/11_polymorphie_und_interfaces/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎭</span>
<span class="sidebar-link-text">11. Polymorphie</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/12_exceptions_und_fehlerbehandlung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🛡️</span>
<span class="sidebar-link-text">12. Exceptions & Fehler</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/13_persistenz_json_und_csv/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💾</span>
<span class="sidebar-link-text">13. Persistenz (JSON/CSV)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/14_gui_mit_tkinter/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🖥️</span>
<span class="sidebar-link-text">14. Desktop-GUIs (Tkinter)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/15_parameter_und_container/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎒</span>
<span class="sidebar-link-text">15. Container & *args</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏆</span>
<span class="sidebar-link-text">16. Master-Abschlussprojekt</span>
</a>
</div>
</div>
<!-- LEHRPFAD 4 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>⚡ 4. Data Engineering</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">17–22</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="index.html" class="sidebar-link active">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">17. Reguläre Ausdrücke (re)</span>
</a>
<a href="../18_funktionale_programmierung_lambda/index.html" class="sidebar-link">
<span class="sidebar-link-icon">⚡</span>
<span class="sidebar-link-text">18. Funktionale Programmierung</span>
</a>
<a href="../19_generatoren_und_itertools/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌊</span>
<span class="sidebar-link-text">19. Generatoren & itertools</span>
</a>
<a href="../20_dataclasses_und_typehints/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏷️</span>
<span class="sidebar-link-text">20. Dataclasses & Types</span>
</a>
<a href="../21_datenbanken_und_sqlite/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗄️</span>
<span class="sidebar-link-text">21. Datenbanken & SQLite</span>
</a>
<a href="../22_web_apis_und_json_feeds/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌐</span>
<span class="sidebar-link-text">22. Web-APIs & JSON</span>
</a>
</div>
</div>
</div>
<div class="sidebar-footer">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="font-size: 0.85rem; font-weight: 700; color: white;" id="sidebar-level-text">⭐ Level 1</div>
<div style="font-size: 0.82rem; font-weight: 700; color: #38bdf8;" id="sidebar-xp-text">0 XP</div>
</div>
</div>
</aside>
<div class="app-main">
<header>
<div class="header-container">
<nav class="nav-links">
<a href="../../index.html">🏠 Zur Kursübersicht</a>
<span>&rsaquo;</span>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html">Kapitel 16</a>
<span>&rsaquo;</span>
<span style="color: white; font-weight: 600;">Kapitel 17</span>
</nav>
<div class="header-badge">Kapitel 17 &bull; Data Engineering Track</div>
<h1>Reguläre Ausdrücke (re) – Text-Mining &amp; Validierung 🔍🕵️</h1>
<p>Werde zum Muster-Detektiv! Durchsuche, filtere und validiere unstrukturierte Textdaten mit Pythons mächtiger Regex-Engine.</p>
</div>
</header>
<main class="container">
<!-- 📂 Schnellzugriff & Download aller Dateien dieser Einheit -->
<div style="background: #ffffff; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 14px 20px; margin-bottom: 25px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; box-shadow: var(--shadow-sm);">
<div style="font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 8px;">
<span>📂 Dateien dieser Einheit:</span>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="aufgabe.py" class="btn" style="padding: 6px 14px; font-size: 0.88rem; background: var(--primary);" download>📄 aufgabe.py (Startercode)</a>
<a href="test_aufgabe.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>🧪 test_aufgabe.py (Tests)</a>
<a href="musterloesung.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>💡 musterloesung.py (Lösung)</a>
<a href="README.md" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;">📖 README.md</a>
</div>
</div>
<!-- 1. Die Detektiv-Analogie -->
<section class="card">
<h2>1. Warum Reguläre Ausdrücke (Regex)?</h2>
<p>
Im echten Alltag eines Software-Entwicklers oder Data Engineers sind 80% aller Rohdaten unstrukturiert: E-Mails, Server-Logs, Tweets, Rechnungen oder Kundeneingaben.
Einfache String-Methoden wie <code>.find()</code> oder <code>.split()</code> reichen oft nicht aus.
</p>
<div class="box box-analogie">
<div class="box-title">🕵️ Die Detektiv-Analogie mit der Suchlupe:</div>
<p>
Ein regulärer Ausdruck ist wie ein <strong>Phantombild</strong>:
Statt nach einem festen Text zu suchen (<em>„Suche exakt nach 'Peter'“</em>), suchst du nach einer <strong>Struktur</strong>:
<br><br>
<em>„Suche 2 Großbuchstaben, gefolgt von 2 Ziffern, gefolgt von weiteren Zahlen – egal welche Nummern es genau sind, das ist eine IBAN!“</em>
</p>
</div>
</section>
<!-- 2. Metazeichen Cheat Sheet -->
<section class="card">
<h2>2. Cheat-Sheet: Die wichtigsten Metazeichen &amp; Quantifizierer</h2>
<p>
In Python nutzen wir das Modul <code>import re</code>. Regex-Muster werden als <strong>Raw-Strings</strong> (<code>r"..."</code>) definiert:
</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);">Zeichen</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Kategorie</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Bedeutung</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Beispiel</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>\d</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Metazeichen</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Beliebige Ziffer (0-9)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"\d{4}"</code> &rarr; <code>"2026"</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>\w</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Metazeichen</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Wortzeichen (Buchstabe, Ziffer, Unterstrich)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"#\w+"</code> &rarr; <code>"#python"</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>\s</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Metazeichen</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Whitespace (Leerzeichen, Tab, Umbruch)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"\s+"</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>.</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Metazeichen</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Beliebiges Zeichen (außer Zeilenumbruch)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"a.c"</code> &rarr; <code>"abc"</code>, <code>"a9c"</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>+</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Quantifizierer</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Mindestens 1 Mal (1 bis unendlich)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"\d+"</code> &rarr; <code>"42"</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>*</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Quantifizierer</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">0 oder mehrmals (optional beliebig oft)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"ab*c"</code> &rarr; <code>"ac"</code>, <code>"abc"</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>?</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Quantifizierer</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Optional (0 oder 1 Mal)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"https?"</code> &rarr; <code>"http"</code>, <code>"https"</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>{min,max}</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Quantifizierer</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Von min bis max Wiederholungen</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"\d{2,4}"</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>^</code> und <code>$</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Anker</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Zeilenanfang (<code>^</code>) und Zeilenende (<code>$</code>)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>r"^\d+$"</code> (nur Ziffern)</td>
</tr>
</tbody>
</table>
</section>
<!-- 3. Methoden des re-Moduls -->
<section class="card">
<h2>3. Die 4 Haupt-Methoden in Python</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);">Methode</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Zweck</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>re.search()</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Sucht den ersten Treffer irgendwo im String.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>m = re.search(r"\d+", "Raum 404")</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>re.fullmatch()</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Prüft, ob der gesamte String dem Muster entspricht (Validierung).</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>re.fullmatch(r"^[a-z]+@[a-z]+\.[a-z]{2,}$", mail)</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>re.findall()</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Sammelt alle Vorkommnisse in einer Python-Liste.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>re.findall(r"#\w+", tweet)</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>re.sub()</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Sucht Treffer und ersetzt sie (auch per Lambda/Funktion).</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>re.sub(r"\d", "*", "Pin: 1234")</code> &rarr; <code>"Pin: ****"</code></td>
</tr>
</tbody>
</table>
<div class="box box-tipp">
<div class="box-title">💡 Benannte Gruppen mit (?P&lt;name&gt;...):</div>
<p>Mit benannten Gruppen kannst du Daten direkt in ein lesbares Dictionary extrahieren:</p>
<div class="code-container">
<pre><span class="kw">import</span> <span class="var">re</span>
<span class="var">pattern</span> = <span class="str">r"^(?P&lt;jahr&gt;\d{4})-(?P&lt;monat&gt;\d{2})-(?P&lt;tag&gt;\d{2})$"</span>
<span class="var">match</span> = <span class="var">re</span>.<span class="fn">fullmatch</span>(<span class="var">pattern</span>, <span class="str">"2026-08-29"</span>)
<span class="kw">if</span> <span class="var">match</span>:
<span class="fn">print</span>(<span class="var">match</span>.<span class="fn">groupdict</span>())
<span class="cm"># Output: {'jahr': '2026', 'monat': '08', 'tag': '29'}</span></pre>
</div>
</div>
</section>
<!-- 4. Aufgaben-Checkliste -->
<section class="card">
<h2>4. 📋 Deine Aufgaben-Checkliste</h2>
<p>Öffne die Datei <code><a href="aufgabe.py" style="color: inherit; text-decoration: underline;">aufgabe.py</a></code> und löse die 5 Teilziele:</p>
<ul class="task-list">
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t17_1">
<label for="t17_1" class="task-label">
<strong>TODO 1 (E-Mail Validierung):</strong> <code>ist_gueltige_email(email: str) -> bool</code> mit <code>re.fullmatch()</code> und TLD-Längenprüfung (&ge; 2).
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t17_2">
<label for="t17_2" class="task-label">
<strong>TODO 2 (Telefonnummern finden):</strong> <code>finde_telefonnummern(text: str) -> list[str]</code> für Formate wie <code>0171-1234567</code>, <code>+49 171 1234567</code> und <code>(089) 123456</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t17_3">
<label for="t17_3" class="task-label">
<strong>TODO 3 (Hashtags extrahieren):</strong> <code>extrahiere_hashtags(text: str) -> list[str]</code> mit Unterstützung für Umlaute und Zahlen.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t17_4">
<label for="t17_4" class="task-label">
<strong>TODO 4 (Datenschutz / IBAN maskieren):</strong> <code>maskiere_iban(text: str) -> str</code> mit <code>re.sub()</code> (erste 4 Zeichen bleiben sichtbar).
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t17_5">
<label for="t17_5" class="task-label">
<strong>TODO 5 (ISO-Datum & benannte Gruppen):</strong> <code>parse_datum_iso(datum_str: str) -> dict[str, str]</code> mit <code>(?P&lt;jahr&gt;...)</code> und <code>groupdict()</code>.
</label>
</li>
</ul>
<h3>💡 Lösungshinweise (Aufklappen bei Bedarf)</h3>
<details>
<summary>🔍 Tipp zu TODO 4: IBAN-Maskierung mit Lambda</summary>
<div class="details-content">
<p>Übergebe an <code>re.sub()</code> eine Funktion, die das Match-Objekt zerlegt:</p>
<div class="code-container">
<pre><span class="var">pattern</span> = <span class="str">r"\b([A-Z]{2}\d{2})([A-Z0-9]{8,30})\b"</span>
<span class="kw">return</span> <span class="var">re</span>.<span class="fn">sub</span>(
<span class="var">pattern</span>,
<span class="kw">lambda</span> <span class="var">m</span>: <span class="var">m</span>.<span class="fn">group</span>(<span class="num">1</span>) + (<span class="str">"*"</span> * <span class="fn">len</span>(<span class="var">m</span>.<span class="fn">group</span>(<span class="num">2</span>))),
<span class="var">text</span>
)</pre>
</div>
</div>
</details>
<details>
<summary>🔍 Tipp zu TODO 5: ISO-Datum mit benannten Gruppen</summary>
<div class="details-content">
<p>Nutze <code>re.fullmatch</code> und <code>groupdict()</code>:</p>
<div class="code-container">
<pre><span class="var">pattern</span> = <span class="str">r"^(?P&lt;jahr&gt;\d{4})-(?P&lt;monat&gt;\d{2})-(?P&lt;tag&gt;\d{2})$"</span>
<span class="var">match</span> = <span class="var">re</span>.<span class="fn">fullmatch</span>(<span class="var">pattern</span>, <span class="var">datum_str</span>.<span class="fn">strip</span>())
<span class="kw">if</span> <span class="kw">not</span> <span class="var">match</span>:
<span class="kw">raise</span> <span class="fn">ValueError</span>(<span class="str">"Ungültiges Format"</span>)
<span class="kw">return</span> <span class="var">match</span>.<span class="fn">groupdict</span>()</pre>
</div>
</div>
</details>
</section>
<!-- 5. Testen & Navigation -->
<section class="card">
<h2>5. 🧪 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 17_regulaere_ausdruecke_re)</span>
</div>
<pre><code>python3 test_aufgabe.py</code></pre>
</div>
<h3>2. Interaktives 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="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="btn btn-secondary">&larr; Zurück zu Kapitel 16 (Master-Projekt)</a>
<a href="../../index.html" class="btn btn-secondary">🏠 Kursübersicht</a>
<a href="../18_funktionale_programmierung_lambda/index.html" class="btn">Weiter zu Kapitel 18 (Funktionale Programmierung) &rarr;</a>
</div>
</section>
</main>
</div>
</div>
<button type="button" class="sidebar-mobile-toggle"><span>☰ Menü</span></button>
<div class="sidebar-overlay"></div>
<!-- Scripts -->
<script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script>
<script src="../../assets/interpreter.js"></script>
<script src="../../assets/certificate.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/runner.js"></script>
<script src="../../assets/ui_enhancements.js"></script>
<script src="../../assets/sidebar.js"></script>
</body>
</html>

View file

@ -0,0 +1,77 @@
"""
Kapitel 17: Reguläre Ausdrücke (re) – Musterlösung 💡
====================================================
Vollständige Referenzlösung für alle Teilaufgaben aus Kapitel 17.
"""
import re
from typing import List, Dict, Optional, Any
def ist_gueltige_email(email: str) -> bool:
"""
Prüft, ob ein übergebener String eine gültige E-Mail-Adresse ist.
"""
pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$"
return bool(re.fullmatch(pattern, email.strip()) if email == email.strip() else False)
def finde_telefonnummern(text: str) -> List[str]:
"""
Findet alle Telefonnummern in einem Text (Standard, Vorwahl, Ländervorwahl).
"""
pattern = r"(?:\+\d{1,3}[ -]\d{2,5}|\(\d{2,5}\)|0\d{1,5})[- /]?\d{3,10}\b"
return re.findall(pattern, text)
def extrahiere_hashtags(text: str) -> List[str]:
"""
Extrahiert alle Social-Media-Hashtags aus einem Text.
"""
pattern = r"#[a-zA-Z0-9_äöüÄÖÜß]+"
return re.findall(pattern, text)
def maskiere_iban(text: str) -> str:
"""
Maskiert alle IBANs im Text: Die ersten 4 Stellen bleiben sichtbar, der Rest wird mit '*' maskiert.
"""
pattern = r"\b([A-Z]{2}\d{2})([A-Z0-9]{8,30})\b"
def _mask_match(match: re.Match) -> str:
prefix = match.group(1)
rest = match.group(2)
return prefix + ("*" * len(rest))
return re.sub(pattern, _mask_match, text)
def parse_datum_iso(datum_str: str) -> Dict[str, str]:
"""
Parst ein Datum im ISO-Format (YYYY-MM-DD) mithilfe benannter Gruppen (?P<name>...).
"""
pattern = r"^(?P<jahr>\d{4})-(?P<monat>\d{2})-(?P<tag>\d{2})$"
match = re.fullmatch(pattern, datum_str.strip())
if not match:
raise ValueError(f"Ungültiges ISO-Datumsformat: '{datum_str}' (erwartet: YYYY-MM-DD)")
return match.groupdict()
def parse_log_zeile(log_zeile: str) -> Dict[str, str]:
"""
Parst eine Server-Logzeile und extrahiert timestamp, level, service und message.
"""
pattern = r"^\[(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(?P<level>[A-Z]+)\] \[(?P<service>[\w-]+)\] (?P<message>.+)$"
match = re.match(pattern, log_zeile.strip())
if not match:
raise ValueError(f"Ungültige Logzeile: '{log_zeile}'")
return match.groupdict()
if __name__ == "__main__":
print("Musterlösung Kapitel 17:")
print("E-Mail validieren:", ist_gueltige_email("user.name@sub.example.com"))
print("Telefonnummern:", finde_telefonnummern("Tel: 0171-1234567 und +49 171 1234567 sowie (089) 123456"))
print("Hashtags:", extrahiere_hashtags("Lerne #python mit #code_2026! #spaß"))
print("IBAN:", maskiere_iban("DE89370400440532013000"))
print("Datum:", parse_datum_iso("2026-08-29"))

View file

@ -0,0 +1,179 @@
import unittest
import sys
from pathlib import Path
# Sicherstellen, dass das aktuelle Verzeichnis im Python-Pfad liegt
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel17(unittest.TestCase):
"""
Testsuite für Kapitel 17: Reguläre Ausdrücke (re) – Text-Mining & Validierung.
"""
def setUp(self):
self.ist_gueltige_email = getattr(aufgabe, "ist_gueltige_email", None)
self.finde_telefonnummern = getattr(aufgabe, "finde_telefonnummern", None)
self.extrahiere_hashtags = getattr(aufgabe, "extrahiere_hashtags", None)
self.maskiere_iban = getattr(aufgabe, "maskiere_iban", None)
self.parse_datum_iso = getattr(aufgabe, "parse_datum_iso", None)
self.parse_log_zeile = getattr(aufgabe, "parse_log_zeile", None)
self.assertIsNotNone(self.ist_gueltige_email, "Funktion 'ist_gueltige_email' nicht gefunden!")
self.assertIsNotNone(self.finde_telefonnummern, "Funktion 'finde_telefonnummern' nicht gefunden!")
self.assertIsNotNone(self.extrahiere_hashtags, "Funktion 'extrahiere_hashtags' nicht gefunden!")
self.assertIsNotNone(self.maskiere_iban, "Funktion 'maskiere_iban' nicht gefunden!")
self.assertIsNotNone(self.parse_datum_iso, "Funktion 'parse_datum_iso' nicht gefunden!")
# ==========================================================================
# 1. E-Mail Validierung
# ==========================================================================
def test_01_ist_gueltige_email(self):
"""Prüft E-Mail Validierung für Standard- und Grenzfälle."""
# Gültige E-Mails
gueltige = [
"max.mustermann@schule.at",
"anna_meier@firma.de",
"dev_123+tag@gmail.com",
"info@sub.domain.org",
"contact@company.co.uk",
"a@b.at",
"test-user@domain.io"
]
for mail in gueltige:
self.assertTrue(self.ist_gueltige_email(mail), f"'{mail}' sollte als GÜLTIG erkannt werden!")
# Ungültige E-Mails
ungueltige = [
"kein_at_zeichen.de",
"@domain.com",
"name@.com",
"name@domain",
"name@domain.c", # TLD zu kurz (mind. 2 Zeichen)
"name @domain.com", # Leerzeichen im Benutzernamen
"name@ domain.com", # Leerzeichen in der Domain
"name@domain .com", # Leerzeichen vor TLD
"name@@domain.com", # Doppeltes @
"", # Leerer String
"name@domain..com" # Doppelte Punkte
]
for mail in ungueltige:
self.assertFalse(self.ist_gueltige_email(mail), f"'{mail}' sollte als UNGÜLTIG erkannt werden!")
# ==========================================================================
# 2. Telefonnummern-Extraktion
# ==========================================================================
def test_02_finde_telefonnummern(self):
"""Prüft das Extrahieren von Telefonnummern in verschiedenen Formaten."""
text = (
"Hier sind unsere Kontakte: Büro 0171-1234567 oder Zentrale +49 171 1234567. "
"In Österreich erreichst du uns unter +43 664 1234567. "
"München: (089) 123456 und Berlin: 030/98765432. "
"Keine Telefonnummer ist 123 oder das Jahr 2026."
)
gefunden = self.finde_telefonnummern(text)
# Sollte alle 5 Telefonnummern finden
self.assertIn("0171-1234567", gefunden)
self.assertIn("+49 171 1234567", gefunden)
self.assertIn("+43 664 1234567", gefunden)
self.assertIn("(089) 123456", gefunden)
self.assertIn("030/98765432", gefunden)
# Grenzfall: Text ohne Nummern
self.assertEqual(self.finde_telefonnummern("Hier gibt es keine Nummern!"), [])
self.assertEqual(self.finde_telefonnummern(""), [])
# ==========================================================================
# 3. Hashtag-Extraktion
# ==========================================================================
def test_03_extrahiere_hashtags(self):
"""Prüft das Extrahieren von Hashtags inkl. Umlauten und Zahlen."""
tweet = "Heute lernen wir #python und #data_engineering! Macht super #spaß und ist #süß. #2026 #AI_pro"
hashtags = self.extrahiere_hashtags(tweet)
erwartet = ["#python", "#data_engineering", "#spaß", "#süß", "#2026", "#AI_pro"]
self.assertEqual(hashtags, erwartet)
# Einzelne Rauten oder Rauten ohne Wortzeichen dürfen nicht matchen
self.assertEqual(self.extrahiere_hashtags("Nur ein # oder #! oder #+#"), [])
self.assertEqual(self.extrahiere_hashtags("Keine Hashtags vorhanden."), [])
self.assertEqual(self.extrahiere_hashtags(""), [])
# ==========================================================================
# 4. IBAN-Maskierung
# ==========================================================================
def test_04_maskiere_iban(self):
"""Prüft die datenschutzkonforme Maskierung von IBANs."""
# Deutsche IBAN (22 Zeichen -> 4 sichtbar + 18 Sternchen)
text1 = "Konto: DE89370400440532013000 bitte prüfen."
ergebnis1 = self.maskiere_iban(text1)
self.assertEqual(ergebnis1, "Konto: DE89****************** bitte prüfen.")
# Mehrere IBANs in einem Text
text2 = "Überweise von AT611904300234573201 an DE1234567890 sofort."
ergebnis2 = self.maskiere_iban(text2)
# AT IBAN (20 Zeichen -> 4 sichtbar + 16 Sternchen)
# DE IBAN (12 Zeichen -> 4 sichtbar + 8 Sternchen)
self.assertEqual(ergebnis2, "Überweise von AT61**************** an DE12******** sofort.")
# Text ohne IBAN bleibt unberührt
text3 = "Keine Bankdaten hier vorhanden."
self.assertEqual(self.maskiere_iban(text3), text3)
self.assertEqual(self.maskiere_iban(""), "")
# ==========================================================================
# 5. ISO-Datum Parsing mit benannten Gruppen
# ==========================================================================
def test_05_parse_datum_iso(self):
"""Prüft das Parsen von ISO-Datumsstrings in ein Dict mit benannten Gruppen."""
res1 = self.parse_datum_iso("2026-08-29")
self.assertEqual(res1, {"jahr": "2026", "monat": "08", "tag": "29"})
res2 = self.parse_datum_iso("1999-12-31")
self.assertEqual(res2, {"jahr": "1999", "monat": "12", "tag": "31"})
# Ungültige Formate müssen ValueError werfen
with self.assertRaises(ValueError, msg="Deutsches Datumsformat muss ValueError auslösen!"):
self.parse_datum_iso("29.08.2026")
with self.assertRaises(ValueError, msg="Slash-Format muss ValueError auslösen!"):
self.parse_datum_iso("2026/08/29")
with self.assertRaises(ValueError, msg="Kurzes Jahr muss ValueError auslösen!"):
self.parse_datum_iso("26-08-29")
with self.assertRaises(ValueError, msg="Leerer String muss ValueError auslösen!"):
self.parse_datum_iso("")
# ==========================================================================
# 6. Bonus: Logfile Parser
# ==========================================================================
def test_06_parse_log_zeile(self):
"""Prüft das Parsen von Server-Logzeilen (Bonus-Funktion)."""
if self.parse_log_zeile is None:
return # Überspringen, falls Bonus nicht implementiert
zeile = "[2026-08-29 14:32:00] [ERROR] [auth_service] Login fehlgeschlagen für user 'admin'"
try:
res = self.parse_log_zeile(zeile)
if res is not None:
self.assertEqual(res.get("timestamp"), "2026-08-29 14:32:00")
self.assertEqual(res.get("level"), "ERROR")
self.assertEqual(res.get("service"), "auth_service")
self.assertEqual(res.get("message"), "Login fehlgeschlagen für user 'admin'")
# Ungültige Zeile
with self.assertRaises(ValueError):
self.parse_log_zeile("Keine gültige Logzeile")
except NotImplementedError:
pass
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,152 @@
# Kapitel 18: Funktionale Programmierung & Pythonic Code ⚡🔄
Willkommen zu **Kapitel 18** in **Lehrpfad 4 (Professional Data Engineering)**!
In diesem Modul lernst du, wie du mithilfe funktionaler Konzepte sauberen, ausdrucksstarken und hocheffizienten Python-Code schreibst (*Pythonic Code*).
---
## 🧭 Didaktischer Hintergrund: Die Fließband-Analogie
In der traditionellen (imperativen) Programmierung schreibst du oft lange Schleifen mit Hilfsvariablen, Indizes und Zwischenlisten:
```python
# ❌ Umständlich / Imperativ
ergebnis = []
i = 0
for element in elemente:
if element % 2 != 0:
ergebnis.append(element ** 2)
i += 1
```
> 🏭 **Die Fließband-Analogie:**
> In der funktionalen Programmierung baust du ein **automatisiertes Fließband** auf:
> - **`filter()` (Die Prüfstation):** Lässt nur Elemente durch, die eine Bedingung erfüllen.
> - **`map()` (Die Bearbeitungsstation):** Transformiert jedes durchlaufende Teil.
> - **`zip()` (Die Montagestation):** Führt zwei parallele Bänder paarweise zusammen.
> - **`enumerate()` (Die Etikettierstation):** Versieht jedes Teil mit einer laufenden Nummer.
---
## 🧰 1. Die funktionalen Werkzeuge im Überblick
### A) `enumerate(iterable, start=0)` – Zähler ohne Hilfsvariable
```python
sprachen = ["Python", "Java", "Rust"]
# Mit Startwert 1 nummerieren
for index, sprache in enumerate(sprachen, start=1):
print(f"{index}. {sprache}")
# Als nummerierte Liste erzeugen:
nummeriert = [f"{i}. {s}" for i, s in enumerate(sprachen, start=1)]
# -> ['1. Python', '2. Java', '3. Rust']
```
---
### B) `zip(*iterables)` – Paralleles Durchlaufen
```python
artikel = ["Apfel", "Banane", "Orange"]
preise = [0.99, 1.49, 1.99]
katalog = dict(zip(artikel, preise))
# -> {'Apfel': 0.99, 'Banane': 1.49, 'Orange': 1.99}
```
---
### C) Anonyme Lambda-Funktionen (`lambda`)
Ein `lambda` ist eine kleine, namenlose Funktion für den Einmalgebrauch:
$$\text{lambda } x: \text{ausdruck}$$
```python
# Normale Funktion:
def verdopple(x):
return x * 2
# Als Lambda-Einzeiler:
verdopple_lambda = lambda x: x * 2
```
---
### D) `map()` & `filter()` – Die Transformations-Pipeline
```python
zahlen = [1, 2, 3, 4, 5, 6]
# 1. Nur ungerade Zahlen herausfiltern (1, 3, 5)
ungerade = filter(lambda x: x % 2 != 0, zahlen)
# 2. Alle verbliebenen Zahlen quadrieren (1, 9, 25)
quadriert = map(lambda x: x ** 2, ungerade)
ergebnis = list(quadriert) # -> [1, 9, 25]
```
---
### E) Custom Sorting mit `key=lambda`
Sortiere komplexe Datenstrukturen nach beliebigen Kriterien:
```python
team = [
{"name": "Alice", "alter": 30, "gehalt": 65000},
{"name": "Bob", "alter": 22, "gehalt": 48000},
{"name": "Charlie", "alter": 45, "gehalt": 80000}
]
# Nach Alter aufsteigend sortieren
nach_alter = sorted(team, key=lambda p: p["alter"])
# Nach Gehalt absteigend sortieren
nach_gehalt = sorted(team, key=lambda p: p["gehalt"], reverse=True)
# Höchstverdiener finden
chef = max(team, key=lambda p: p["gehalt"])
```
---
### F) Kontrollfunktionen: `all()` und `any()`
```python
alter_gruppe = [20, 25, 18, 30]
# Sind ALLE Personen mindestens 18?
print(all(alter >= 18 for alter in alter_gruppe)) # -> True
# Gibt es MINDESTENS EINE Person unter 18?
print(any(alter < 18 for alter in alter_gruppe)) # -> False
```
---
## 📑 Vergleich: Imperativ vs. Funktional (Pythonic)
| Anwendungsfall | Imperativ (Klassisch) | Funktional & Pythonic |
| :--- | :--- | :--- |
| **Nummerierung** | `i = 1; for x in l: print(i, x); i += 1` | `for i, x in enumerate(l, 1): print(i, x)` |
| **Paarung** | `for i in range(len(a)): dict[a[i]] = b[i]` | `dict(zip(a, b))` |
| **Transformation** | `res = []; for x in l: res.append(x*2)` | `list(map(lambda x: x*2, l))` |
| **Filterung** | `res = []; for x in l: if x>0: res.append(x)` | `list(filter(lambda x: x>0, l))` |
| **Validierung** | `ok = True; for x in l: if not cond(x): ok=False` | `all(cond(x) for x in l)` |
---
## 📁 Die Dateien in diesem Modul
- **`index.html`**: Interaktive Lernseite mit Cheat-Sheet, Code-Pipelines, Checkliste und aufklappbaren Tipps.
- **`aufgabe.py`**: Dein Arbeitsblatt mit Type Hints und TODOs für `enumerate`, `zip`, `map`/`filter`, `sorted` und `all`/`any`.
- **`test_aufgabe.py`**: Automatische Unittest-Suite zur Validierung deiner Lösungen.
- **`musterloesung.py`**: Vollständig ausprogrammierte Referenzlösung.
---
## 🚀 Schnellstart
```bash
# 1. Bearbeite aufgabe.py
# 2. Führe die automatischen Unittests aus:
python3 test_aufgabe.py
# 3. Oder starte aufgabe.py direkt im Terminal:
python3 aufgabe.py
```

View file

@ -0,0 +1,242 @@
r"""
Kapitel 18: Funktionale Programmierung & Pythonic Code ⚡🔄
=========================================================
Aufgabe: Schreibe eleganten, expressiven und hocheffizienten Python-Code!
Nutze funktionale Werkzeuge wie lambda, enumerate, zip, map, filter,
sorted(..., key=...) sowie any und all zur Datenverarbeitung.
Themen:
1. enumerate(iterable, start=0) – Index und Wert elegant durchlaufen
2. zip(iter_a, iter_b) – Sequenzen parallel verknüpfen
3. Lambda-Funktionen – Anonyme Einzeiler (lambda x: ...)
4. map() & filter() – Transformation & Filterung auf Datenströmen
5. Custom Sorting – sorted() & max()/min() mit key=lambda ...
6. Kontrollfunktionen – any() und all() für Datenvalidierung
"""
from typing import List, Dict, Any, Optional
# ==============================================================================
# TEIL 1: ENUMERATE – NUMMERIERTE LISTEN
# ==============================================================================
def nummerierte_liste(elemente: List[str], start: int = 1) -> List[str]:
"""
TODO 1: Erstellt eine nummerierte Liste von Strings mit `enumerate()`.
Parameter:
elemente (List[str]): Eine Liste von Zeichenketten (z.B. ["Python", "Java", "Rust"]).
start (int): Die Startnummer für die Aufzählung (Standard: 1).
Rückgabe:
List[str]: Eine formatierte Liste wie ["1. Python", "2. Java", "3. Rust"].
Falls `elemente` leer ist, soll eine leere Liste [] zurückgegeben werden.
Beispiele:
nummerierte_liste(["Python", "Java", "Rust"])
-> ["1. Python", "2. Java", "3. Rust"]
nummerierte_liste(["A", "B"], start=0)
-> ["0. A", "1. B"]
Tipp:
Nutze eine List Comprehension mit enumerate:
[f"{i}. {item}" for i, item in enumerate(elemente, start=start)]
"""
# 🎯 TEILZIEL 1: Implementiere nummerierte_liste mit enumerate()
pass
# ==============================================================================
# TEIL 2: ZIP – SEQUENZEN VERKNÜPFEN (KATALOG ERSTELLEN)
# ==============================================================================
def kombiniere_katalog(artikel: List[str], preise: List[float]) -> Dict[str, float]:
"""
TODO 2: Verknüpft zwei getrennte Listen (Artikelnamen und Preise) mithilfe
von `zip()` zu einem Produktkatalog-Dictionary.
Parameter:
artikel (List[str]): Liste der Artikelbezeichnungen.
preise (List[float]): Liste der entsprechenden Preise.
Rückgabe:
Dict[str, float]: Ein Dictionary mit {artikel: preis}.
Falls eine Liste kürzer ist, schneidet `zip()` automatisch ab.
Beispiele:
kombiniere_katalog(["Apfel", "Banane", "Orange"], [0.99, 1.49, 1.99])
-> {"Apfel": 0.99, "Banane": 1.49, "Orange": 1.99}
Tipp:
dict(zip(artikel, preise)) erzeugt direkt das fertige Dictionary!
"""
# 🎯 TEILZIEL 2: Kombiniere Listen mit zip()
pass
# ==============================================================================
# TEIL 3: MAP, FILTER & LAMBDA – DATEN PIPELINE
# ==============================================================================
def quadriere_und_filtere(zahlen: List[int]) -> List[int]:
"""
TODO 3: Filtert alle ungeraden Zahlen aus der Eingabeliste und quadriert diese.
Nutze dafür funktionale Methoden (`filter`, `map` mit `lambda`) oder Comprehensions.
Regeln:
- 1. Schritt (Filter): Behalte nur ungerade Zahlen (z.B. x % 2 != 0).
- 2. Schritt (Map): Berechne das Quadrat jeder ungeraden Zahl (x ** 2).
Beispiele:
quadriere_und_filtere([1, 2, 3, 4, 5])
-> Ungerade: [1, 3, 5] -> Quadriert: [1, 9, 25]
quadriere_und_filtere([2, 4, 6])
-> []
quadriere_und_filtere([-3, -2, 0, 3])
-> [9, 9]
Tipp mit funktionaler Pipeline:
ungerade = filter(lambda x: x % 2 != 0, zahlen)
quadriert = map(lambda x: x ** 2, ungerade)
return list(quadriert)
"""
# 🎯 TEILZIEL 3: Implementiere die funktionale Transformation
pass
# ==============================================================================
# TEIL 4: CUSTOM SORTING – SORTIEREN MIT KEY=LAMBDA
# ==============================================================================
def sortiere_personen_nach_alter(personen: List[Dict[str, Any]], absteigend: bool = False) -> List[Dict[str, Any]]:
"""
TODO 4: Sortiert eine Liste von Personen-Dictionaries anhand des Schlüssels 'alter'.
Parameter:
personen (List[Dict[str, Any]]): z.B. [{'name': 'Alice', 'alter': 30}, {'name': 'Bob', 'alter': 22}]
absteigend (bool): False = aufsteigend (Standard), True = absteigend (älteste zuerst).
Rückgabe:
List[Dict[str, Any]]: Eine neue, sortierte Liste von Dictionaries.
Beispiele:
team = [{'name': 'Alice', 'alter': 30}, {'name': 'Bob', 'alter': 22}, {'name': 'Charlie', 'alter': 45}]
sortiere_personen_nach_alter(team)
-> [{'name': 'Bob', 'alter': 22}, {'name': 'Alice', 'alter': 30}, {'name': 'Charlie', 'alter': 45}]
Tipp:
sorted(personen, key=lambda p: p["alter"], reverse=absteigend)
"""
# 🎯 TEILZIEL 4: Sortiere mit sorted() und Lambda-Key
pass
# ==============================================================================
# TEIL 5: KONTROLLFUNKTIONEN ALL & ANY
# ==============================================================================
def alle_volljaehrig(alter_liste: List[int], mindestalter: int = 18) -> bool:
"""
TODO 5: Prüft mit `all()`, ob ausnahmslos ALLE Personen in der Liste
mindestens das `mindestalter` erreicht haben.
Parameter:
alter_liste (List[int]): Liste mit Altersangaben in Jahren.
mindestalter (int): Das geforderte Mindestalter (Standard: 18).
Rückgabe:
bool: True wenn alle >= mindestalter (oder Liste leer ist), sonst False.
Beispiele:
alle_volljaehrig([19, 25, 42, 18]) -> True
alle_volljaehrig([19, 17, 25, 30]) -> False (17 ist unter 18)
alle_volljaehrig([]) -> True (leere Menge erfüllt Kriterium trivial)
Tipp:
all(alter >= mindestalter for alter in alter_liste)
"""
# 🎯 TEILZIEL 5: Prüfe Volljährigkeit mit all()
pass
def mindestens_ein_treffer(ergebnisse: List[bool]) -> bool:
"""
TODO 6: Prüft mit `any()`, ob mindestens ein Element in der Liste True ist.
Parameter:
ergebnisse (List[bool]): Liste von Wahrheitswerten.
Rückgabe:
bool: True wenn mindestens ein Element True ist, sonst False (bei leerer Liste False).
Beispiele:
mindestens_ein_treffer([False, False, True, False]) -> True
mindestens_ein_treffer([False, False, False]) -> False
mindestens_ein_treffer([]) -> False
Tipp:
return any(ergebnisse)
"""
# 🎯 TEILZIEL 6: Prüfe Treffer mit any()
pass
# ==============================================================================
# TEIL 7: BONUS: EXTREMWERTE FINDEN MIT MAX / MIN UND KEY=LAMBDA
# ==============================================================================
def finde_extrem_produkt(produkte: List[Dict[str, Any]], modus: str = "teuerstes") -> Dict[str, Any]:
"""
TODO 7 (Bonus): Findet das teuerste oder günstigste Produkt in einer Liste.
Parameter:
produkte (List[Dict[str, Any]]): z.B. [{'titel': 'Laptop', 'preis': 999.0}, ...]
modus (str): "teuerstes" (nutzt max()) oder "billigstes" (nutzt min()).
Rückgabe:
Dict[str, Any]: Das Produkt-Dictionary mit dem maximalen/minimalen Preis.
Exceptions:
ValueError: Wenn `produkte` leer ist oder `modus` weder "teuerstes" noch "billigstes" ist.
Tipp:
max(produkte, key=lambda p: p["preis"]) bzw. min(produkte, key=lambda p: p["preis"])
"""
# 🎯 TEILZIEL 7: Finde Extremprodukt mit max/min(..., key=lambda)
pass
if __name__ == "__main__":
print("=" * 60)
print("⚡ KAPITEL 18: FUNKTIONALE PROGRAMMIERUNG TESTLAUF")
print("=" * 60)
# 1. Enumerate
sprachen = ["Python", "TypeScript", "Rust", "Go"]
print("Nummerierte Sprachen:", nummerierte_liste(sprachen))
# 2. Zip
artikel = ["Kaffee", "Tee", "Kuchen"]
preise = [3.50, 2.80, 4.20]
print("Katalog:", kombiniere_katalog(artikel, preise))
# 3. Map & Filter
zahlen = [1, 2, 3, 4, 5, 6, 7]
print("Ungerade quadriert:", quadriere_und_filtere(zahlen))
# 4. Custom Sorting
team = [
{"name": "Alice", "alter": 28},
{"name": "Bob", "alter": 22},
{"name": "Charlie", "alter": 35}
]
print("Team sortiert (jung -> alt):", sortiere_personen_nach_alter(team))
# 5. All & Any
altersgruppe = [20, 22, 19, 18]
print("Alle volljährig?:", alle_volljaehrig(altersgruppe))
print("Mindestens ein Treffer?:", mindestens_ein_treffer([False, False, True]))

View file

@ -0,0 +1,449 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapitel 18: Funktionale Programmierung & Pythonic Code</title>
<link rel="stylesheet" href="../../assets/style.css">
</head>
<body>
<div class="app-layout">
<aside class="app-sidebar">
<div class="sidebar-header">
<a href="../../index.html" class="sidebar-brand-link">
<div class="sidebar-logo">🐍</div>
<div>
<div class="sidebar-title">Python Portal</div>
<div class="sidebar-subtitle">Lernplattform</div>
</div>
</a>
</div>
<div class="sidebar-quick-nav">
<a href="../../index.html" class="sidebar-link">🏠 Startseite</a>
<a href="../../index.html#tutorial" class="sidebar-link">📖 Tutorial &amp; Workflow</a>
<a href="../../index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a>
</div>
<div class="sidebar-nav">
<!-- LEHRPFAD 1 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🌱 1. Grundlagen</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">G01–G10</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_1_grundlagen/01_erste_schritte_taschenrechner/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧮</span>
<span class="sidebar-link-text">G01. Taschenrechner</span>
</a>
<a href="../../lehrpfad_1_grundlagen/02_variablen_und_datentypen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📦</span>
<span class="sidebar-link-text">G02. Variablen & Typen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/03_ein_und_ausgabe/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💬</span>
<span class="sidebar-link-text">G03. Ein- & Ausgabe</span>
</a>
<a href="../../lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚦</span>
<span class="sidebar-link-text">G04. Bedingungen (if)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎡</span>
<span class="sidebar-link-text">G05. Schleifen (for/while)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/06_funktionen_und_module/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🪄</span>
<span class="sidebar-link-text">G06. Funktionen & Module</span>
</a>
<a href="../../lehrpfad_1_grundlagen/07_listen_und_sequenzen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📋</span>
<span class="sidebar-link-text">G07. Listen & Sequenzen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/08_textverarbeitung_und_strings/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔤</span>
<span class="sidebar-link-text">G08. String-Methoden</span>
</a>
<a href="../../lehrpfad_1_grundlagen/09_dictionaries_und_sets/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗃️</span>
<span class="sidebar-link-text">G09. Dicts & Sets</span>
</a>
<a href="../../lehrpfad_1_grundlagen/10_comprehensions_datum_algorithmen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚀</span>
<span class="sidebar-link-text">G10. Comprehensions</span>
</a>
</div>
</div>
<!-- LEHRPFAD 2 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🏗️ 2. OOP Einstieg</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">00–06</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">00. Fehlersuche & Debug</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/01_einstieg_klassen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏗️</span>
<span class="sidebar-link-text">01. Einstieg in Klassen</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/02_init_und_self/index.html" class="sidebar-link">
<span class="sidebar-link-icon">⚙️</span>
<span class="sidebar-link-text">02. Konstruktor & self</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/03_methoden_und_verhalten/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏎️</span>
<span class="sidebar-link-text">03. Methoden & Verhalten</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04_str_und_darstellung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧾</span>
<span class="sidebar-link-text">04. __str__ Darstellung</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04b_umstieg_vscode/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💻</span>
<span class="sidebar-link-text">04b. VS Code Setup</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04c_git_und_versionskontrolle/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌿</span>
<span class="sidebar-link-text">04c. Git-Versionskontrolle</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/05_objekte_kombinieren/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎵</span>
<span class="sidebar-link-text">05. Objekte kombinieren</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🥚</span>
<span class="sidebar-link-text">06. Projekt: Tamagotchi</span>
</a>
</div>
</div>
<!-- LEHRPFAD 3 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🚀 3. Fortgeschritten</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">07–16</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_3_fortgeschrittenes_oop/07_referenzen_und_speicher/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧠</span>
<span class="sidebar-link-text">07. Referenzen & Speicher</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/08_operator_overloading_dunder/index.html" class="sidebar-link">
<span class="sidebar-link-icon">➕</span>
<span class="sidebar-link-text">08. Operator Overloading</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧪</span>
<span class="sidebar-link-text">09. Unit Tests & TDD</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧬</span>
<span class="sidebar-link-text">10. Vererbung & super()</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/11_polymorphie_und_interfaces/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎭</span>
<span class="sidebar-link-text">11. Polymorphie</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/12_exceptions_und_fehlerbehandlung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🛡️</span>
<span class="sidebar-link-text">12. Exceptions & Fehler</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/13_persistenz_json_und_csv/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💾</span>
<span class="sidebar-link-text">13. Persistenz (JSON/CSV)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/14_gui_mit_tkinter/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🖥️</span>
<span class="sidebar-link-text">14. Desktop-GUIs (Tkinter)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/15_parameter_und_container/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎒</span>
<span class="sidebar-link-text">15. Container & *args</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏆</span>
<span class="sidebar-link-text">16. Master-Abschlussprojekt</span>
</a>
</div>
</div>
<!-- LEHRPFAD 4 -->
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>⚡ 4. Data Engineering</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">17–22</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../17_regulaere_ausdruecke_re/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">17. Reguläre Ausdrücke (re)</span>
</a>
<a href="index.html" class="sidebar-link active">
<span class="sidebar-link-icon">⚡</span>
<span class="sidebar-link-text">18. Funktionale Programmierung</span>
</a>
<a href="../19_generatoren_und_itertools/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌊</span>
<span class="sidebar-link-text">19. Generatoren & itertools</span>
</a>
<a href="../20_dataclasses_und_typehints/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏷️</span>
<span class="sidebar-link-text">20. Dataclasses & Types</span>
</a>
<a href="../21_datenbanken_und_sqlite/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗄️</span>
<span class="sidebar-link-text">21. Datenbanken & SQLite</span>
</a>
<a href="../22_web_apis_und_json_feeds/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌐</span>
<span class="sidebar-link-text">22. Web-APIs & JSON</span>
</a>
</div>
</div>
</div>
<div class="sidebar-footer">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="font-size: 0.85rem; font-weight: 700; color: white;" id="sidebar-level-text">⭐ Level 1</div>
<div style="font-size: 0.82rem; font-weight: 700; color: #38bdf8;" id="sidebar-xp-text">0 XP</div>
</div>
</div>
</aside>
<div class="app-main">
<header>
<div class="header-container">
<nav class="nav-links">
<a href="../../index.html">🏠 Zur Kursübersicht</a>
<span>&rsaquo;</span>
<a href="../17_regulaere_ausdruecke_re/index.html">Kapitel 17</a>
<span>&rsaquo;</span>
<span style="color: white; font-weight: 600;">Kapitel 18</span>
</nav>
<div class="header-badge">Kapitel 18 &bull; Data Engineering Track</div>
<h1>Funktionale Programmierung &amp; Pythonic Code ⚡🔄</h1>
<p>Schreibe eleganten, kompakten und hocheffizienten Python-Code mit Lambda, enumerate, zip, map, filter, custom sort und Kontrollfunktionen.</p>
</div>
</header>
<main class="container">
<!-- 📂 Schnellzugriff & Download aller Dateien dieser Einheit -->
<div style="background: #ffffff; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 14px 20px; margin-bottom: 25px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; box-shadow: var(--shadow-sm);">
<div style="font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 8px;">
<span>📂 Dateien dieser Einheit:</span>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="aufgabe.py" class="btn" style="padding: 6px 14px; font-size: 0.88rem; background: var(--primary);" download>📄 aufgabe.py (Startercode)</a>
<a href="test_aufgabe.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>🧪 test_aufgabe.py (Tests)</a>
<a href="musterloesung.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>💡 musterloesung.py (Lösung)</a>
<a href="README.md" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;">📖 README.md</a>
</div>
</div>
<!-- 1. Die Fließband-Analogie -->
<section class="card">
<h2>1. Warum funktionale Idiome in Python?</h2>
<p>
In Python gibt es oft zwei Wege, eine Aufgabe zu lösen: Den langen, imperativen Weg mit vielen Hilfsvariablen und verschachtelten Schleifen – oder den eleganten, <strong>funktionalen Weg (Pythonic Code)</strong>.
</p>
<div class="box box-analogie">
<div class="box-title">🏭 Die automatisierte Fließband-Analogie:</div>
<p>
Stelle dir eine moderne Fertigungsstraße vor:
</p>
<ul>
<li><strong><code>enumerate()</code> (Die Etikettierstation):</strong> Klebt an jedes Teil automatisch die aktuelle Positionsnummer.</li>
<li><strong><code>zip()</code> (Die Fügestation):</strong> Bringt Bauteile von zwei parallelen Fließbändern paarweise zusammen.</li>
<li><strong><code>filter()</code> (Die Qualitätsweiche):</strong> Lässt nur Bauteile passieren, die ein Prüfkriterium erfüllen.</li>
<li><strong><code>map()</code> (Die Veredelungsstation):</strong> Wendet eine Transformation auf jedes durchlaufende Teil an.</li>
<li><strong><code>all()</code> / <code>any()</code> (Die Sensorüberwachung):</strong> Meldet sofort, ob alle Teile fehlerfrei sind oder mindestens ein Alarm anliegt.</li>
</ul>
</div>
</section>
<!-- 2. Cheat-Sheet Tabelle -->
<section class="card">
<h2>2. Cheat-Sheet: Funktionale Idiome auf einen Blick</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);">Funktion / Idiom</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Zweck</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Typisches Code-Beispiel</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Ergebnis</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>enumerate(l, start)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Iteriert gleichzeitig über Index und Element.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>[f"{i}. {x}" for i, x in enumerate(["A","B"], 1)]</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>['1. A', '2. B']</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>zip(a, b)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Verknüpft mehrere Sequenzen paarweise.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>dict(zip(["Name", "Alter"], ["Max", 25]))</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>{'Name': 'Max', 'Alter': 25}</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>lambda x: ...</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Anonyme Mini-Funktion als Einzeiler.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>verdoppeln = lambda x: x * 2</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>verdoppeln(5) &rarr; 10</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>map(f, iter)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Wendet Funktion <code>f</code> auf alle Elemente an.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>list(map(lambda x: x**2, [1, 2, 3]))</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>[1, 4, 9]</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>filter(p, iter)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Filtert Elemente nach Prädikat <code>p</code>.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>list(filter(lambda x: x > 0, [-2, 0, 5]))</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>[5]</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>sorted(..., key=...)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Sortiert nach beliebigem Schlüssel (Lambda).</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>sorted(user, key=lambda u: u["alter"])</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Sortierte Liste</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>all(iter)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Gibt <code>True</code>, wenn ALLE Elemente wahr sind.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>all(x >= 18 for x in [20, 25, 18])</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>True</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>any(iter)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Gibt <code>True</code>, wenn MINDESTENS EIN Element wahr ist.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>any(x == 0 for x in [1, 2, 0, 4])</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>True</code></td>
</tr>
</tbody>
</table>
</section>
<!-- 3. Aufgaben-Checkliste -->
<section class="card">
<h2>3. 📋 Deine Aufgaben-Checkliste</h2>
<p>Öffne die Datei <code><a href="aufgabe.py" style="color: inherit; text-decoration: underline;">aufgabe.py</a></code> und löse die 6 Kern-Aufgaben:</p>
<ul class="task-list">
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t18_1">
<label for="t18_1" class="task-label">
<strong>TODO 1 (enumerate):</strong> <code>nummerierte_liste(elemente, start=1)</code> formatiert Listen als <code>"1. Python"</code> etc.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t18_2">
<label for="t18_2" class="task-label">
<strong>TODO 2 (zip):</strong> <code>kombiniere_katalog(artikel, preise)</code> baut ein Dictionary aus zwei separaten Sequenzen.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t18_3">
<label for="t18_3" class="task-label">
<strong>TODO 3 (map & filter):</strong> <code>quadriere_und_filtere(zahlen)</code> filtert ungerade Werte und quadriert sie funktional.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t18_4">
<label for="t18_4" class="task-label">
<strong>TODO 4 (Custom Sort):</strong> <code>sortiere_personen_nach_alter(personen, absteigend=False)</code> mit <code>key=lambda p: p["alter"]</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t18_5">
<label for="t18_5" class="task-label">
<strong>TODO 5 (all):</strong> <code>alle_volljaehrig(alter_liste, mindestalter=18)</code> prüft Volljährigkeit aller Gruppenmitglieder.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t18_6">
<label for="t18_6" class="task-label">
<strong>TODO 6 (any):</strong> <code>mindestens_ein_treffer(ergebnisse)</code> prüft auf mindestens einen wahren Treffer.
</label>
</li>
</ul>
<h3>💡 Lösungshinweise (Aufklappen bei Bedarf)</h3>
<details>
<summary>🔍 Tipp zu TODO 3: Map & Filter verketten</summary>
<div class="details-content">
<p>Du kannst <code>filter()</code> direkt in <code>map()</code> schachteln und mit <code>list()</code> materialisieren:</p>
<div class="code-container">
<pre><span class="kw">return</span> <span class="fn">list</span>(<span class="fn">map</span>(<span class="kw">lambda</span> <span class="var">x</span>: <span class="var">x</span> ** <span class="num">2</span>, <span class="fn">filter</span>(<span class="kw">lambda</span> <span class="var">x</span>: <span class="var">x</span> % <span class="num">2</span> != <span class="num">0</span>, <span class="var">zahlen</span>)))</pre>
</div>
</div>
</details>
<details>
<summary>🔍 Tipp zu TODO 4: Sortieren mit Lambda-Key</summary>
<div class="details-content">
<p>Nutze den <code>key</code>-Parameter von <code>sorted()</code>:</p>
<div class="code-container">
<pre><span class="kw">return</span> <span class="fn">sorted</span>(<span class="var">personen</span>, <span class="var">key</span>=<span class="kw">lambda</span> <span class="var">p</span>: <span class="var">p</span>[<span class="str">"alter"</span>], <span class="var">reverse</span>=<span class="var">absteigend</span>)</pre>
</div>
</div>
</details>
</section>
<!-- 4. Testen & Navigation -->
<section class="card">
<h2>4. 🧪 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 18_funktionale_programmierung_lambda)</span>
</div>
<pre><code>python3 test_aufgabe.py</code></pre>
</div>
<h3>2. Interaktives 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="../17_regulaere_ausdruecke_re/index.html" class="btn btn-secondary">&larr; Zurück zu Kapitel 17 (Reguläre Ausdrücke)</a>
<a href="../../index.html" class="btn btn-secondary">🏠 Kursübersicht</a>
<a href="../19_generatoren_und_itertools/index.html" class="btn">Weiter zu Kapitel 19 (Generatoren & itertools) &rarr;</a>
</div>
</section>
</main>
</div>
</div>
<button type="button" class="sidebar-mobile-toggle"><span>☰ Menü</span></button>
<div class="sidebar-overlay"></div>
<!-- Scripts -->
<script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script>
<script src="../../assets/interpreter.js"></script>
<script src="../../assets/certificate.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/runner.js"></script>
<script src="../../assets/ui_enhancements.js"></script>
<script src="../../assets/sidebar.js"></script>
</body>
</html>

View file

@ -0,0 +1,76 @@
"""
Kapitel 18: Funktionale Programmierung & Pythonic Code – Musterlösung 💡
========================================================================
Vollständige Referenzlösung für alle Teilaufgaben aus Kapitel 18.
"""
from typing import List, Dict, Any, Optional
def nummerierte_liste(elemente: List[str], start: int = 1) -> List[str]:
"""
Erstellt eine nummerierte Liste von Strings mit enumerate().
"""
return [f"{i}. {item}" for i, item in enumerate(elemente, start=start)]
def kombiniere_katalog(artikel: List[str], preise: List[float]) -> Dict[str, float]:
"""
Verknüpft Artikelnamen und Preise mittels zip() zu einem Dictionary.
"""
return dict(zip(artikel, preise))
def quadriere_und_filtere(zahlen: List[int]) -> List[int]:
"""
Filtert ungerade Zahlen und quadriert sie funktional mit map() und filter().
"""
return list(map(lambda x: x ** 2, filter(lambda x: x % 2 != 0, zahlen)))
def sortiere_personen_nach_alter(personen: List[Dict[str, Any]], absteigend: bool = False) -> List[Dict[str, Any]]:
"""
Sortiert eine Liste von Personen-Dictionaries anhand von 'alter' mit sorted() und key=lambda.
"""
return sorted(personen, key=lambda p: p["alter"], reverse=absteigend)
def alle_volljaehrig(alter_liste: List[int], mindestalter: int = 18) -> bool:
"""
Prüft mit all(), ob alle Personen in der Liste mindestens mindestalter Jahre alt sind.
"""
return all(alter >= mindestalter for alter in alter_liste)
def mindestens_ein_treffer(ergebnisse: List[bool]) -> bool:
"""
Prüft mit any(), ob mindestens ein Element True ist.
"""
return any(ergebnisse)
def finde_extrem_produkt(produkte: List[Dict[str, Any]], modus: str = "teuerstes") -> Dict[str, Any]:
"""
Findet das teuerste oder günstigste Produkt mit max() / min() und key=lambda.
"""
if not produkte:
raise ValueError("Produktliste darf nicht leer sein!")
if modus == "teuerstes":
return max(produkte, key=lambda p: p["preis"])
elif modus == "billigstes":
return min(produkte, key=lambda p: p["preis"])
else:
raise ValueError(f"Ungültiger Modus '{modus}'. Erlaubt sind 'teuerstes' und 'billigstes'.")
if __name__ == "__main__":
print("Musterlösung Kapitel 18:")
print("1. Enumerate:", nummerierte_liste(["Python", "Rust", "Go"]))
print("2. Zip:", kombiniere_katalog(["Kaffee", "Kuchen"], [3.5, 4.2]))
print("3. Map/Filter:", quadriere_und_filtere([1, 2, 3, 4, 5]))
team = [{"name": "A", "alter": 30}, {"name": "B", "alter": 20}]
print("4. Custom Sort:", sortiere_personen_nach_alter(team))
print("5. all():", alle_volljaehrig([20, 21, 18]))
print("6. any():", mindestens_ein_treffer([False, True]))
items = [{"titel": "A", "preis": 10}, {"titel": "B", "preis": 50}]
print("7. Extremwert:", finde_extrem_produkt(items, "teuerstes"))

View file

@ -0,0 +1,182 @@
import unittest
import sys
from pathlib import Path
# Sicherstellen, dass das aktuelle Verzeichnis im Python-Pfad liegt
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel18(unittest.TestCase):
"""
Testsuite für Kapitel 18: Funktionale Programmierung & Pythonic Code.
"""
def setUp(self):
self.nummerierte_liste = getattr(aufgabe, "nummerierte_liste", None)
self.kombiniere_katalog = getattr(aufgabe, "kombiniere_katalog", None)
self.quadriere_und_filtere = getattr(aufgabe, "quadriere_und_filtere", None)
self.sortiere_personen_nach_alter = getattr(aufgabe, "sortiere_personen_nach_alter", None)
self.alle_volljaehrig = getattr(aufgabe, "alle_volljaehrig", None)
self.mindestens_ein_treffer = getattr(aufgabe, "mindestens_ein_treffer", None)
self.finde_extrem_produkt = getattr(aufgabe, "finde_extrem_produkt", None)
self.assertIsNotNone(self.nummerierte_liste, "Funktion 'nummerierte_liste' nicht gefunden!")
self.assertIsNotNone(self.kombiniere_katalog, "Funktion 'kombiniere_katalog' nicht gefunden!")
self.assertIsNotNone(self.quadriere_und_filtere, "Funktion 'quadriere_und_filtere' nicht gefunden!")
self.assertIsNotNone(self.sortiere_personen_nach_alter, "Funktion 'sortiere_personen_nach_alter' nicht gefunden!")
self.assertIsNotNone(self.alle_volljaehrig, "Funktion 'alle_volljaehrig' nicht gefunden!")
self.assertIsNotNone(self.mindestens_ein_treffer, "Funktion 'mindestens_ein_treffer' nicht gefunden!")
# ==========================================================================
# 1. Enumerate – Nummerierte Listen
# ==========================================================================
def test_01_nummerierte_liste(self):
"""Prüft enumerate() mit Standardstartwert 1 und benutzerdefiniertem Start."""
sprachen = ["Python", "Java", "Rust"]
ergebnis = self.nummerierte_liste(sprachen)
self.assertEqual(ergebnis, ["1. Python", "2. Java", "3. Rust"])
# Startwert 0
ergebnis_0 = self.nummerierte_liste(["A", "B", "C"], start=0)
self.assertEqual(ergebnis_0, ["0. A", "1. B", "2. C"])
# Startwert 10
ergebnis_10 = self.nummerierte_liste(["Erster", "Zweiter"], start=10)
self.assertEqual(ergebnis_10, ["10. Erster", "11. Zweiter"])
# Leere Liste
self.assertEqual(self.nummerierte_liste([]), [])
# ==========================================================================
# 2. Zip – Katalog-Kombination
# ==========================================================================
def test_02_kombiniere_katalog(self):
"""Prüft zip() zur Kombination von Artikeln und Preisen."""
artikel = ["Apfel", "Banane", "Orange"]
preise = [0.99, 1.49, 1.99]
katalog = self.kombiniere_katalog(artikel, preise)
self.assertEqual(katalog, {"Apfel": 0.99, "Banane": 1.49, "Orange": 1.99})
# Unterschiedliche Längen (zip schneidet ab)
katalog_kurz = self.kombiniere_katalog(["Kaffee", "Tee"], [3.50, 2.80, 9.99])
self.assertEqual(katalog_kurz, {"Kaffee": 3.50, "Tee": 2.80})
# Leere Listen
self.assertEqual(self.kombiniere_katalog([], []), {})
# ==========================================================================
# 3. Map & Filter & Lambda – Pipeline
# ==========================================================================
def test_03_quadriere_und_filtere(self):
"""Prüft Filterung ungerader Zahlen und deren Quadrierung."""
# Gemischte Zahlen
zahlen = [1, 2, 3, 4, 5, 6, 7]
ergebnis = self.quadriere_und_filtere(zahlen)
self.assertEqual(ergebnis, [1, 9, 25, 49])
# Nur gerade Zahlen -> Leeres Ergebnis
self.assertEqual(self.quadriere_und_filtere([2, 4, 6, 8]), [])
# Negative ungerade Zahlen: (-3)^2 = 9
self.assertEqual(self.quadriere_und_filtere([-3, -2, -1, 0, 1]), [9, 1, 1])
# Leere Liste
self.assertEqual(self.quadriere_und_filtere([]), [])
# ==========================================================================
# 4. Custom Sorting mit Key=Lambda
# ==========================================================================
def test_04_sortiere_personen_nach_alter(self):
"""Prüft benutzerdefiniertes Sortieren nach Alter (aufsteigend & absteigend)."""
personen = [
{"name": "Charlie", "alter": 45},
{"name": "Alice", "alter": 30},
{"name": "Bob", "alter": 22},
{"name": "Dora", "alter": 18}
]
# Aufsteigend
sortiert_auf = self.sortiere_personen_nach_alter(personen, absteigend=False)
namen_auf = [p["name"] for p in sortiert_auf]
self.assertEqual(namen_auf, ["Dora", "Bob", "Alice", "Charlie"])
# Absteigend
sortiert_ab = self.sortiere_personen_nach_alter(personen, absteigend=True)
namen_ab = [p["name"] for p in sortiert_ab]
self.assertEqual(namen_ab, ["Charlie", "Alice", "Bob", "Dora"])
# Original-Liste darf nicht verändert werden (neue Liste zurückgeben)
self.assertIsNot(sortiert_auf, personen)
# ==========================================================================
# 5. all() – Volljährigkeit
# ==========================================================================
def test_05_alle_volljaehrig(self):
"""Prüft all() mit Standardmindestalter 18 und dynamischem Mindestalter."""
# Alle >= 18
self.assertTrue(self.alle_volljaehrig([18, 19, 25, 50]))
# Mindestens einer < 18
self.assertFalse(self.alle_volljaehrig([18, 17, 25, 50]))
self.assertFalse(self.alle_volljaehrig([0]))
# Anderes Mindestalter (z.B. 21)
self.assertTrue(self.alle_volljaehrig([21, 25, 30], mindestalter=21))
self.assertFalse(self.alle_volljaehrig([20, 25, 30], mindestalter=21))
# Leere Liste ist trivial True in Python
self.assertTrue(self.alle_volljaehrig([]))
# ==========================================================================
# 6. any() – Mindestens ein Treffer
# ==========================================================================
def test_06_mindestens_ein_treffer(self):
"""Prüft any() auf Wahrheitswerte."""
self.assertTrue(self.mindestens_ein_treffer([False, False, True, False]))
self.assertTrue(self.mindestens_ein_treffer([True, True, True]))
self.assertFalse(self.mindestens_ein_treffer([False, False, False]))
self.assertFalse(self.mindestens_ein_treffer([]))
# ==========================================================================
# 7. Bonus: Extremwert-Finder
# ==========================================================================
def test_07_finde_extrem_produkt(self):
"""Prüft das Finden von Extremwerten mit max/min und key=lambda."""
if self.finde_extrem_produkt is None:
return
produkte = [
{"titel": "Kugelschreiber", "preis": 2.50},
{"titel": "Laptop", "preis": 1299.00},
{"titel": "Kaffeetasse", "preis": 8.90},
{"titel": "Monitor", "preis": 349.00}
]
try:
teuerstes = self.finde_extrem_produkt(produkte, modus="teuerstes")
if teuerstes is not None:
self.assertEqual(teuerstes["titel"], "Laptop")
self.assertEqual(teuerstes["preis"], 1299.00)
billigstes = self.finde_extrem_produkt(produkte, modus="billigstes")
if billigstes is not None:
self.assertEqual(billigstes["titel"], "Kugelschreiber")
self.assertEqual(billigstes["preis"], 2.50)
# Fehlerfälle
with self.assertRaises(ValueError):
self.finde_extrem_produkt([], modus="teuerstes")
with self.assertRaises(ValueError):
self.finde_extrem_produkt(produkte, modus="ungueltig")
except NotImplementedError:
pass
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,147 @@
# Kapitel 19: Generatoren, Iteratoren & itertools (Memory Efficiency & Streaming) 🌊⚡
Willkommen zu **Kapitel 19** im Lehrpfad 4 (*Professional Data Engineering*)!
In diesem Modul lernst du, wie professionelle Software-Entwickler riesige Datenmengen speichereffizient im RAM verarbeiten. Statt Gigabytes an Daten auf einmal in eine Liste zu laden und dein System zum Absturz zu bringen, streamst du Werte **on-demand** Tropfen für Tropfen mit **Generatoren** und dem mächtigen **`itertools`**-Modul.
---
## 🧭 Didaktischer Hintergrund
In Data Pipelines, Web-Servern und Big-Data-Anwendungen stößt klassisches Laden von Daten schnell an physikalische RAM-Grenzen:
- **Speicherüberlauf (Out of Memory):** Eine Liste mit 50 Millionen Objekten belegt mehrere Gigabytes Arbeitsspeicher.
- **Lazy Evaluation (Verzögerte Auswertung):** Werte werden erst in dem exakten Mikrosekunden-Moment berechnet, in dem sie gebraucht werden.
- **Null RAM-Verschwendung:** Ein Generator verbraucht unabhängig davon, ob er 10 oder 10 Milliarden Elemente liefert, immer nur eine winzige, konstante Speichermenge (ca. 100 Bytes)!
---
## 🚰 1. Die Badewannen- vs. Wasserhahn-Analogie
```
🔴 DIE BADEWANNE (Klassische Listen & Arrays)
┌────────────────────────────────────────────────────────┐
│ [Wert 1, Wert 2, Wert 3, ..., Wert 10.000.000] │
│ -> 800 MB RAM belegt! Alles muss VORHER im RAM sein! │
└────────────────────────────────────────────────────────┘
🟢 DER WASSERHAHN (Generatoren & Streams mit yield)
┌────────────────────────────────────────────────────────┐
│ [Wasserhahn] ── Tropfen 1 ──> [Verarbeitung] │
│ ── Tropfen 2 ──> [Verarbeitung] │
│ ── Tropfen 3 ──> [Verarbeitung] │
│ -> ~100 Bytes RAM! Jeder Wert wird nach Gebrauch │
│ wieder verworfen oder weitergeleitet. │
└────────────────────────────────────────────────────────┘
```
---
## 🔄 2. Iterables vs. Iterators (`iter()`, `next()`, `StopIteration`)
In Python gibt es einen fundamentalen Unterschied:
| Begriff | Was ist das? | Wie wird es erzeugt? | Typische Beispiele |
| :--- | :--- | :--- | :--- |
| **Iterable** | Eine Sammlung, über die man iterieren kann (besitzt `__iter__()`). | Listen, Tupel, Strings, Dicts | `[1, 2, 3]`, `"Hallo"` |
| **Iterator** | Das Zustandsobjekt, das den aktuellen Zeiger hält und mit `next()` den nächsten Wert liefert (besitzt `__next__()`). | `it = iter(iterable)` | `iter([1, 2, 3])` |
### Was passiert bei `for x in sammlung:` unter der Haube?
1. Python ruft `it = iter(sammlung)` auf, um einen Iterator zu erhalten.
2. In jeder Schleifenrunde wird `x = next(it)` aufgerufen.
3. Wenn keine Elemente mehr vorhanden sind, löst Python intern die `StopIteration`-Exception aus und beendet die Schleife sauber.
```python
daten = [10, 20, 30]
it = iter(daten)
print(next(it)) # -> 10
print(next(it)) # -> 20
print(next(it)) # -> 30
# print(next(it)) # -> Löst StopIteration aus!
```
---
## 🪄 3. Eigene Generatoren mit `yield`
Das Schlüsselwort **`yield`** verwandelt eine gewöhnliche Funktion in eine Generator-Funktion.
- Ein `return` beendet die Funktion und vernichtet alle lokalen Variablen.
- Ein `yield` pausiert die Funktion, gibt den Zwischenwert zurück und merkt sich den exakten Programmzustand (Zeile, lokale Variablen), um beim nächsten `next()` genau dort weiterzumachen!
```python
def mein_generator():
print("Starte Schritt 1...")
yield "Erster Wert"
print("Mache weiter bei Schritt 2...")
yield "Zweiter Wert"
print("Fertig!")
gen = mein_generator() # Noch kein Code wird ausgeführt!
print(next(gen)) # Druckt: Starte Schritt 1... -> Erster Wert
print(next(gen)) # Druckt: Mache weiter... -> Zweiter Wert
```
### Unendliche Generatoren:
Mit `yield` sind unendliche Sequenzen möglich, ohne dass der Speicher überläuft:
```python
def endlos_zaehler(start=0, schritt=1):
aktuell = start
while True:
yield aktuell
aktuell += schritt
```
---
## ⚡ 4. Generator-Expressions: `(x**2 for x in ...)`
Genauso wie es List Comprehensions `[x for x in ...]` gibt, kannst du mit runden Klammern `(x for x in ...)` eine schlanke **Generator Expression** erzeugen:
```python
# 🔴 List Comprehension: Erzeugt 10 Mio. Zahlen sofort im RAM (ca. 80 MB!)
quadrate_liste = [x**2 for x in range(10_000_000)]
# 🟢 Generator Expression: Kostet ca. 100 Bytes RAM!
quadrate_gen = (x**2 for x in range(10_000_000))
print(next(quadrate_gen)) # 0
print(next(quadrate_gen)) # 1
print(next(quadrate_gen)) # 4
```
---
## 🧰 5. Das `itertools`-Modul (Die High-Performance Toolbox)
Pythons Standardbibliothek liefert mit `import itertools` hochgradig optimierte C-Iteratoren:
| Funktion | Erklärung | Code-Beispiel |
| :--- | :--- | :--- |
| `itertools.chain(*iterables)` | Hängt mehrere Iterables lückenlos hintereinander | `chain([1, 2], [3, 4])` $\rightarrow$ `1, 2, 3, 4` |
| `itertools.cycle(iterable)` | Wiederholt ein Iterable in einer Endlosschleife | `cycle(['A', 'B'])` $\rightarrow$ `A, B, A, B, A...` |
| `itertools.count(start, step)` | Unendlicher Zähler | `count(10, 2)` $\rightarrow$ `10, 12, 14, 16...` |
| `itertools.permutations(p, r)` | Alle geordneten Anordnungen der Länge $r$ | `permutations('AB', 2)` $\rightarrow$ `('A','B'), ('B','A')` |
| `itertools.combinations(p, r)` | Alle ungeordneten Kombinationen ohne Duplikate | `combinations('ABC', 2)` $\rightarrow$ `('A','B'), ('A','C'), ('B','C')` |
| `itertools.islice(iterable, n)`| Schneidet die ersten $n$ Elemente aus einem Stream | `islice(count(), 5)` $\rightarrow$ `0, 1, 2, 3, 4` |
---
## 📁 Die Dateien in diesem Ordner
- **`index.html`**: Interaktive Web-Lernseite mit visuellen Badewannen-Diagrammen, Code-Vergleichen, Cheat-Sheets und Checkliste.
- **`aufgabe.py`**: Dein Arbeitsblatt mit Type Hints und didaktischen TODOs für Generatoren, Filter und `itertools`.
- **`test_aufgabe.py`**: Automatische Unittest-Suite zur Überprüfung aller Anforderungen.
- **`musterloesung.py`**: Vollständig ausprogrammierte Referenzlösung.
---
## 🚀 Schnellstart
```bash
# 1. Bearbeite aufgabe.py
# 2. Führe die automatischen Tests aus:
python3 test_aufgabe.py
# 3. Oder starte das interaktive Benchmark-Skript:
python3 aufgabe.py
```

View file

@ -0,0 +1,271 @@
"""
Kapitel 19: Generatoren, Iteratoren & itertools (Memory Efficiency & Streaming) 🌊⚡
==================================================================================
Lerne speichereffiziente Datenverarbeitung in Python:
1. Iterables vs. Iterators (iter(), next(), StopIteration)
2. Eigene Generatoren mit 'yield' (Werte on-demand generieren, 0 RAM-Verschwendung)
3. Generator-Expressions (x**2 for x in riesige_daten)
4. Das itertools-Modul (chain, cycle, count, combinations, permutations)
Didaktische Analogie:
- "Gefüllte Badewanne" (Listen): Alle Daten liegen gleichzeitig im RAM.
- "Wasserhahn" (Generatoren): Daten fließen Tropfen für Tropfen, genau dann wenn sie gebraucht werden.
"""
import sys
import itertools
from typing import Iterator, Iterable, Any, Union, List, Tuple
# ==============================================================================
# TEIL 1: GRUNDLAGEN VON ITERATOREN & GENERATOREN MIT YIELD
# ==============================================================================
def endlos_zaehler(start: int = 0, schritt: int = 1) -> Iterator[int]:
"""
Ein unendlicher Generator, der Zahlen beginnend bei 'start' mit der
Schrittweite 'schritt' generiert.
Parameter:
start (int): Die Startzahl (Standard: 0).
schritt (int): Die Schrittweite (Standard: 1).
Erzeugt (yield):
int: Die nächste Zahl in der unendlichen Sequenz.
Beispiele:
z = endlos_zaehler(10, 2)
next(z) -> 10
next(z) -> 12
next(z) -> 14
"""
# ==========================================================================
# 🎯 TEILZIEL 1 (TODO 1): Implementiere endlos_zaehler
# 1. Initialisiere eine Variable 'aktuell' mit dem Wert 'start'.
# 2. Nutze eine unendliche Schleife (while True:).
# 3. Gib 'aktuell' mit 'yield' an den Aufrufer zurück.
# 4. Erhöhe 'aktuell' um 'schritt'.
# ==========================================================================
pass
def fibonacci_generator(anzahl: int) -> Iterator[int]:
"""
Ein Generator, der die ersten 'anzahl' Fibonacci-Zahlen on-demand erzeugt.
Die Fibonacci-Folge beginnt mit: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
Parameter:
anzahl (int): Wie viele Fibonacci-Zahlen erzeugt werden sollen (>= 0).
Erzeugt (yield):
int: Die jeweils nächste Fibonacci-Zahl.
Exceptions:
ValueError: Falls 'anzahl' negativ (< 0) ist.
Beispiele:
list(fibonacci_generator(5)) -> [0, 1, 1, 2, 3]
list(fibonacci_generator(0)) -> []
"""
# ==========================================================================
# 🎯 TEILZIEL 2 (TODO 2): Implementiere fibonacci_generator
# 1. Validiere 'anzahl': Wenn anzahl < 0, löse ValueError aus.
# 2. Setze die Startwerte a = 0, b = 1.
# 3. Wiederhole in einer Schleife 'anzahl'-mal:
# - Gib 'a' mit 'yield' zurück.
# - Aktualisiere die Werte mit Tupel-Zuweisung: a, b = b, a + b
# ==========================================================================
pass
# ==============================================================================
# TEIL 2: SPEICHERSCHONENDE FILTER & GENERATOR-EXPRESSIONS
# ==============================================================================
def filter_grosse_zahlen(zahlen_iterable: Iterable[Union[int, float]], schwellenwert: float) -> Iterator[Union[int, float]]:
"""
Ein Generator-Filter, der nur Zahlen liefert, die strikt größer als
der angegebene 'schwellenwert' sind (x > schwellenwert).
Wichtig: Es darf KEINE Zwischenliste im Speicher aufgebaut werden!
Die Werte müssen einzeln gestreamt werden.
Parameter:
zahlen_iterable (Iterable): Eine beliebige Sequenz oder ein Generator von Zahlen.
schwellenwert (float): Der Grenzwert.
Erzeugt (yield):
Union[int, float]: Jede Zahl aus zahlen_iterable, die > schwellenwert ist.
Beispiele:
daten = [10, 5, 20, 3, 50]
list(filter_grosse_zahlen(daten, 15)) -> [20, 50]
"""
# ==========================================================================
# 🎯 TEILZIEL 3 (TODO 3): Implementiere filter_grosse_zahlen
# 1. Iteriere mit einer for-Schleife über jedes Element in 'zahlen_iterable'.
# 2. Prüfe, ob das Element > schwellenwert ist.
# 3. Wenn ja, gib es mit 'yield' weiter.
# ==========================================================================
pass
def erzeuge_quadrat_generator(n: int) -> Iterator[int]:
"""
Erzeugt eine Generator-Expression für Quadratzahlen von 1 bis n (inklusive).
Parameter:
n (int): Die Obergrenze (>= 0).
Rückgabe:
Iterator[int]: Ein Generator-Objekt (x**2 for x in ...), keine Liste!
Exceptions:
ValueError: Falls n < 0 ist.
Beispiele:
gen = erzeuge_quadrat_generator(4)
list(gen) -> [1, 4, 9, 16]
"""
# ==========================================================================
# 🎯 TEILZIEL 4 (TODO 4): Implementiere erzeuge_quadrat_generator
# 1. Wenn n < 0, löse einen ValueError aus.
# 2. Gib direkt eine Generator-Expression zurück: (x**2 for x in range(1, n + 1))
# ==========================================================================
pass
# ==============================================================================
# TEIL 3: DAS ITERTOOLS-MODUL (CHAIN, PERMUTATIONS & CYCLE)
# ==============================================================================
def kette_listen(*listen: Iterable[Any]) -> List[Any]:
"""
Verbindet beliebig viele Iterables (*args) mit 'itertools.chain' zu einer
einzigen flachen Liste.
Parameter:
*listen (Iterable[Any]): Beliebig viele Iterables (Listen, Tupel, Generatoren).
Rückgabe:
List[Any]: Eine zusammenhängende Liste aller Elemente.
Beispiele:
kette_listen([1, 2], [3, 4], [5]) -> [1, 2, 3, 4, 5]
kette_listen([], [10]) -> [10]
"""
# ==========================================================================
# 🎯 TEILZIEL 5 (TODO 5): Implementiere kette_listen mit itertools.chain
# 1. Nutze itertools.chain(*listen) oder itertools.chain.from_iterable(listen).
# 2. Wandle das Ergebnis in eine normale Python-Liste um (list(...)) und gib sie zurück.
# ==========================================================================
pass
def erzeuge_passwort_kombinationen(zeichen: str, laenge: int) -> List[str]:
"""
Erzeugt alle möglichen Permutationen der angegebenen Zeichenkette mit der
exakten Länge 'laenge' unter Verwendung von 'itertools.permutations'.
Parameter:
zeichen (str): Die verfügbaren Zeichen als String (z.B. "abc").
laenge (int): Die Länge der gesuchten Kombinationen.
Rückgabe:
List[str]: Eine Liste von Strings mit allen Permutationen.
Exceptions:
ValueError: Falls laenge < 0 oder laenge > len(zeichen) ist.
Beispiele:
erzeuge_passwort_kombinationen("abc", 2) -> ['ab', 'ac', 'ba', 'bc', 'ca', 'cb']
erzeuge_passwort_kombinationen("ab", 1) -> ['a', 'b']
"""
# ==========================================================================
# 🎯 TEILZIEL 6 (TODO 6): Implementiere erzeuge_passwort_kombinationen
# 1. Validiere: Wenn laenge < 0 oder laenge > len(zeichen), löse ValueError aus.
# 2. Verwende itertools.permutations(zeichen, laenge).
# 3. Führe jedes generierte Tupel von Zeichen mit "".join(...) zu einem String zusammen.
# 4. Gib alle kombinierten Strings als Liste zurück.
# ==========================================================================
pass
def zyklische_elemente(elemente: List[Any], anzahl: int) -> List[Any]:
"""
Nimmt eine Liste von Elementen und liefert die ersten 'anzahl' Elemente
in zyklischer Reihenfolge mit 'itertools.cycle'.
Parameter:
elemente (List[Any]): Die Ausgangsliste (darf nicht leer sein, falls anzahl > 0).
anzahl (int): Wie viele Elemente zyklisch entnommen werden sollen.
Rückgabe:
List[Any]: Liste mit 'anzahl' Elementen.
Exceptions:
ValueError: Falls anzahl < 0 ist oder falls elemente leer ist bei anzahl > 0.
Beispiele:
zyklische_elemente(["Rot", "Gruen", "Blau"], 7)
-> ["Rot", "Gruen", "Blau", "Rot", "Gruen", "Blau", "Rot"]
"""
# ==========================================================================
# 🎯 TEILZIEL 7 (TODO 7): Implementiere zyklische_elemente
# 1. Validiere anzahl >= 0 (sonst ValueError).
# 2. Wenn anzahl == 0, gib eine leere Liste [] zurück.
# 3. Wenn elemente leer ist (und anzahl > 0), löse ValueError aus.
# 4. Erstelle einen zyklischen Iterator mit zyk = itertools.cycle(elemente).
# 5. Hole mit next(zyk) genau 'anzahl' Elemente ab und sammle sie in einer Liste.
# ==========================================================================
pass
# ==============================================================================
# DEMONSTRATION & SPEICHER-BENCHMARK
# ==============================================================================
if __name__ == "__main__":
print("=" * 70)
print("🌊 KAPITEL 19: GENERATOREN & ITERTOOLS (MEMORY EFFICIENCY)")
print("=" * 70)
# 1. Speichervergleich: Liste vs. Generator
elemente_anzahl = 1_000_000
print(f"\n📊 SPEICHERVERGLEICH FÜR {elemente_anzahl:,} ZAHLEN:")
liste = [x for x in range(elemente_anzahl)]
gen = (x for x in range(elemente_anzahl))
speicher_liste = sys.getsizeof(liste) / (1024 * 1024)
speicher_gen = sys.getsizeof(gen)
print(f" 🔴 Liste (Badewanne): {speicher_liste:.2f} MB RAM im Hauptspeicher!")
print(f" 🟢 Generator (Wasserhahn): {speicher_gen} Bytes RAM (Faktor {int(speicher_liste * 1024 * 1024 / speicher_gen):,}x sparsamer!)")
# 2. Endloszähler Test
print("\n⏱️ Endloszähler (erste 5 Werte ab 100 in 10er Schritten):")
zaehler = endlos_zaehler(100, 10)
if zaehler:
for _ in range(5):
print(f" -> {next(zaehler)}")
# 3. Fibonacci Test
print("\n🐰 Die ersten 10 Fibonacci-Zahlen:")
fibs = fibonacci_generator(10)
if fibs:
print(f" -> {list(fibs)}")
# 4. Passwort-Permutationen Test
print("\n🔐 Passwort-Permutationen für 'abc' (Länge 2):")
perms = erzeuge_passwort_kombinationen("abc", 2)
print(f" -> {perms}")
# 5. Zyklischer Taktgeber
print("\n🚦 Ampel-Zyklus (7 Schritte):")
ampel = zyklische_elemente(["Rot", "Gelb", "Gruen"], 7)
print(f" -> {ampel}")
print("\n" + "=" * 70)
print("💡 Starte die Tests mit: python3 test_aufgabe.py")
print("=" * 70)

View file

@ -0,0 +1,502 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapitel 19: Generatoren, Iteratoren & itertools</title>
<link rel="stylesheet" href="../../assets/style.css">
</head>
<body>
<div class="app-layout">
<aside class="app-sidebar">
<div class="sidebar-header">
<a href="../../index.html" class="sidebar-brand-link">
<div class="sidebar-logo">🐍</div>
<div>
<div class="sidebar-title">Python Portal</div>
<div class="sidebar-subtitle">Lernplattform</div>
</div>
</a>
</div>
<div class="sidebar-quick-nav">
<a href="../../index.html" class="sidebar-link">🏠 Startseite</a>
<a href="../../index.html#tutorial" class="sidebar-link">📖 Tutorial &amp; Workflow</a>
<a href="../../index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a>
</div>
<div class="sidebar-nav">
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🌱 1. Grundlagen</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">G01–G10</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_1_grundlagen/01_erste_schritte_taschenrechner/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧮</span>
<span class="sidebar-link-text">G01. Taschenrechner</span>
</a>
<a href="../../lehrpfad_1_grundlagen/02_variablen_und_datentypen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📦</span>
<span class="sidebar-link-text">G02. Variablen & Typen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/03_ein_und_ausgabe/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💬</span>
<span class="sidebar-link-text">G03. Ein- & Ausgabe</span>
</a>
<a href="../../lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚦</span>
<span class="sidebar-link-text">G04. Bedingungen (if)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎡</span>
<span class="sidebar-link-text">G05. Schleifen (for/while)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/06_funktionen_und_module/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🪄</span>
<span class="sidebar-link-text">G06. Funktionen & Module</span>
</a>
<a href="../../lehrpfad_1_grundlagen/07_listen_und_sequenzen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📋</span>
<span class="sidebar-link-text">G07. Listen & Sequenzen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/08_textverarbeitung_und_strings/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔤</span>
<span class="sidebar-link-text">G08. String-Methoden</span>
</a>
<a href="../../lehrpfad_1_grundlagen/09_dictionaries_und_sets/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗃️</span>
<span class="sidebar-link-text">G09. Dicts & Sets</span>
</a>
<a href="../../lehrpfad_1_grundlagen/10_comprehensions_datum_algorithmen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚀</span>
<span class="sidebar-link-text">G10. Comprehensions</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🏗️ 2. OOP Einstieg</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">00–06</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">00. Fehlersuche & Debug</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/01_einstieg_klassen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏗️</span>
<span class="sidebar-link-text">01. Einstieg in Klassen</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/02_init_und_self/index.html" class="sidebar-link">
<span class="sidebar-link-icon">⚙️</span>
<span class="sidebar-link-text">02. Konstruktor & self</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/03_methoden_und_verhalten/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏎️</span>
<span class="sidebar-link-text">03. Methoden & Verhalten</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04_str_und_darstellung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧾</span>
<span class="sidebar-link-text">04. __str__ Darstellung</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04b_umstieg_vscode/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💻</span>
<span class="sidebar-link-text">04b. VS Code Setup</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04c_git_und_versionskontrolle/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌿</span>
<span class="sidebar-link-text">04c. Git-Versionskontrolle</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/05_objekte_kombinieren/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎵</span>
<span class="sidebar-link-text">05. Objekte kombinieren</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🥚</span>
<span class="sidebar-link-text">06. Projekt: Tamagotchi</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🚀 3. Fortgeschritten</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">07–16</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_3_fortgeschrittenes_oop/07_referenzen_und_speicher/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧠</span>
<span class="sidebar-link-text">07. Referenzen & Speicher</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/08_operator_overloading_dunder/index.html" class="sidebar-link">
<span class="sidebar-link-icon">➕</span>
<span class="sidebar-link-text">08. Operator Overloading</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧪</span>
<span class="sidebar-link-text">09. Unit Tests & TDD</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧬</span>
<span class="sidebar-link-text">10. Vererbung & super()</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/11_polymorphie_und_interfaces/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎭</span>
<span class="sidebar-link-text">11. Polymorphie</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/12_exceptions_und_fehlerbehandlung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🛡️</span>
<span class="sidebar-link-text">12. Exceptions & Fehler</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/13_persistenz_json_und_csv/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💾</span>
<span class="sidebar-link-text">13. Persistenz (JSON/CSV)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/14_gui_mit_tkinter/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🖥️</span>
<span class="sidebar-link-text">14. Desktop-GUIs (Tkinter)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/15_parameter_und_container/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎒</span>
<span class="sidebar-link-text">15. Container & *args</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏆</span>
<span class="sidebar-link-text">16. Master-Abschlussprojekt</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>⚡ 4. Data Engineering</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">19–20</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools/index.html" class="sidebar-link active">
<span class="sidebar-link-icon">🌊</span>
<span class="sidebar-link-text">19. Generatoren & itertools</span>
</a>
<a href="../../lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏷️</span>
<span class="sidebar-link-text">20. Dataclasses & Types</span>
</a>
</div>
</div>
</div>
<div class="sidebar-footer">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="font-size: 0.85rem; font-weight: 700; color: white;" id="sidebar-level-text">⭐ Level 1</div>
<div style="font-size: 0.82rem; font-weight: 700; color: #38bdf8;" id="sidebar-xp-text">0 XP</div>
</div>
</div>
</aside>
<div class="app-main">
<header>
<div class="header-container">
<nav class="nav-links">
<a href="../../index.html">🏠 Zur Kursübersicht</a>
<span>&rsaquo;</span>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html">Kapitel 16</a>
<span>&rsaquo;</span>
<span style="color: white; font-weight: 600;">Kapitel 19</span>
</nav>
<div class="header-badge">Lehrpfad 4 &bull; Professional Data Engineering</div>
<h1>Generatoren, Iteratoren &amp; itertools 🌊⚡</h1>
<p>Verarbeite riesige Datenströme mit 0 RAM-Verschwendung: Entdecke das Geheimnis von <code>yield</code>, Lazy Evaluation und Pythons High-Performance-Modul <code>itertools</code>.</p>
</div>
</header>
<main class="container">
<!-- 📂 Schnellzugriff & Download aller Dateien dieser Einheit -->
<div style="background: #ffffff; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 14px 20px; margin-bottom: 25px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; box-shadow: var(--shadow-sm);">
<div style="font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 8px;">
<span>📂 Dateien dieser Einheit:</span>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="aufgabe.py" class="btn" style="padding: 6px 14px; font-size: 0.88rem; background: var(--primary);" download>📄 aufgabe.py (Startercode)</a>
<a href="test_aufgabe.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>🧪 test_aufgabe.py (Tests)</a>
<a href="musterloesung.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>💡 musterloesung.py (Lösung)</a>
<a href="README.md" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;">📖 README.md</a>
</div>
</div>
<!-- 1. Das Konzept: Badewanne vs. Wasserhahn -->
<section class="card">
<h2>1. Warum Generatoren? Die Badewannen-Analogie 🚰🛁</h2>
<p>
Stelle dir vor, du möchtest 10 Millionen Sensor-Messwerte analysieren. Wie verarbeitet ein Rechner diese Daten?
</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin: 20px 0;">
<div style="background: var(--rose-light); border: 2px solid var(--rose); border-radius: var(--radius-md); padding: 16px;">
<h3 style="color: var(--rose); margin-top: 0;">🔴 Die Badewanne (Klassische Listen)</h3>
<p style="font-size: 0.9rem;">Du lässt erst <strong>die gesamte Badewanne vollaufen</strong> (alle 10 Mio. Werte im RAM erzeugen), bevor du einen Schluck nimmst.</p>
<div class="code-container" style="margin: 8px 0;">
<pre><span class="cm"># Belegt ~80 MB bis 1 GB RAM!</span>
<span class="var">daten</span> = [<span class="var">x</span>**<span class="num">2</span> <span class="kw">for</span> <span class="var">x</span> <span class="kw">in</span> <span class="fn">range</span>(<span class="num">10_000_000</span>)]</pre>
</div>
<span style="font-weight: 700; color: #881337; font-size: 0.85rem;">⚠️ Gefahr: Out of Memory Crash bei Big Data!</span>
</div>
<div style="background: var(--emerald-light); border: 2px solid var(--emerald); border-radius: var(--radius-md); padding: 16px;">
<h3 style="color: var(--emerald); margin-top: 0;">🟢 Der Wasserhahn (Generatoren mit yield)</h3>
<p style="font-size: 0.9rem;">Du drehst den Hahn auf: Die Daten fließen <strong>Tropfen für Tropfen (on-demand)</strong> direkt in deinen Mund.</p>
<div class="code-container" style="margin: 8px 0;">
<pre><span class="cm"># Belegt nur ~104 Bytes RAM!</span>
<span class="var">daten</span> = (<span class="var">x</span>**<span class="num">2</span> <span class="kw">for</span> <span class="var">x</span> <span class="kw">in</span> <span class="fn">range</span>(<span class="num">10_000_000</span>))</pre>
</div>
<span style="font-weight: 700; color: #064e3b; font-size: 0.85rem;">✨ 100.000x speichereffizienter &amp; ultraschnell!</span>
</div>
</div>
<div class="box box-analogie">
<div class="box-title">💡 Kernprinzip: Lazy Evaluation (Verzögerte Auswertung)</div>
<p>Ein Generator berechnet den nächsten Wert erst in der exakten Millisekunde, in der du ihn mit <code>next()</code> oder in einer <code>for</code>-Schleife abfragst!</p>
</div>
</section>
<!-- 2. Iterables vs. Iterators -->
<section class="card">
<h2>2. Das Protokoll: Iterable vs. Iterator 🔄</h2>
<p>Jedes Mal, wenn du in Python eine <code>for</code>-Schleife schreibst, passiert Folgendes:</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);">Konzept</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Magische Methode</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Bedeutung</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);"><strong>Iterable</strong></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>__iter__()</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Objekt, über das iteriert werden kann.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>liste = [1, 2, 3]</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Iterator</strong></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>__next__()</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Zeiger-Objekt, das den aktuellen Zustand hält.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>it = iter(liste); next(it)</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>StopIteration</strong></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Exception</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Wird automatisch geworfen, wenn alle Werte verbraucht sind.</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Beendet <code>for</code>-Schleifen lautlos.</td>
</tr>
</tbody>
</table>
<div class="code-container">
<div class="code-header"><span>Iteratoren manuell steuern mit iter() und next()</span></div>
<pre><span class="var">farben</span> = [<span class="str">"Rot"</span>, <span class="str">"Gruen"</span>, <span class="str">"Blau"</span>]
<span class="var">it</span> = <span class="fn">iter</span>(<span class="var">farben</span>)
<span class="fn">print</span>(<span class="fn">next</span>(<span class="var">it</span>)) <span class="cm"># -> "Rot"</span>
<span class="fn">print</span>(<span class="fn">next</span>(<span class="var">it</span>)) <span class="cm"># -> "Gruen"</span>
<span class="fn">print</span>(<span class="fn">next</span>(<span class="var">it</span>)) <span class="cm"># -> "Blau"</span>
<span class="cm"># next(it) wirft jetzt StopIteration!</span></pre>
</div>
</section>
<!-- 3. Eigene Generatoren mit yield -->
<section class="card">
<h2>3. Magie im Einsatz: Eigene Generatoren mit <code>yield</code> 🪄</h2>
<p>
Im Gegensatz zu <code>return</code> (welches eine Funktion komplett beendet) unterbricht <strong><code>yield</code></strong> die Funktion nur kurzzeitig, gibt den Wert zurück und friert den gesamten Zustand (lokale Variablen, Zeilennummer) ein!
</p>
<div class="code-container">
<div class="code-header"><span>Endloszähler &amp; Fibonacci mit yield</span></div>
<pre><span class="kw">def</span> <span class="fn">endlos_zaehler</span>(<span class="var">start</span>=<span class="num">0</span>, <span class="var">schritt</span>=<span class="num">1</span>):
<span class="var">aktuell</span> = <span class="var">start</span>
<span class="kw">while</span> <span class="kw">True</span>:
<span class="kw">yield</span> <span class="var">aktuell</span> <span class="cm"># Pausiert hier und gibt den Wert zurück</span>
<span class="var">aktuell</span> += <span class="var">schritt</span> <span class="cm"># Macht beim nächsten next() genau hier weiter!</span>
<span class="var">z</span> = <span class="fn">endlos_zaehler</span>(<span class="num">10</span>, <span class="num">5</span>)
<span class="fn">print</span>(<span class="fn">next</span>(<span class="var">z</span>)) <span class="cm"># -> 10</span>
<span class="fn">print</span>(<span class="fn">next</span>(<span class="var">z</span>)) <span class="cm"># -> 15</span>
<span class="fn">print</span>(<span class="fn">next</span>(<span class="var">z</span>)) <span class="cm"># -> 20</span></pre>
</div>
</section>
<!-- 4. Das itertools Modul -->
<section class="card">
<h2>4. Das <code>itertools</code> Power-Modul 🧰</h2>
<p>
Das eingebaute Modul <code>itertools</code> bietet blitzschnelle, in C geschriebene Iterator-Bausteine:
</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);">Werkzeug</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Zweck</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Beispiel</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>itertools.chain(*iterables)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Verkettet mehrere Listen/Generatoren ohne Kopieren</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>chain([1, 2], [3, 4]) &rarr; 1, 2, 3, 4</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>itertools.cycle(iterable)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Wiederholt eine Liste unendlich oft im Kreis</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>cycle(['Rot', 'Gruen']) &rarr; Rot, Gruen, Rot...</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>itertools.permutations(p, r)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Alle geordneten Anordnungen (Passwort-Cracker)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>permutations('AB', 2) &rarr; ('A','B'), ('B','A')</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>itertools.combinations(p, r)</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Alle ungeordneten Teilmengen (Lotto, Teams)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>combinations('ABC', 2) &rarr; ('A','B'), ('A','C'), ('B','C')</code></td>
</tr>
</tbody>
</table>
</section>
<!-- 5. Aufgaben & Checkliste -->
<section class="card">
<h2>5. 🎯 Deine Aufgaben in <code>aufgabe.py</code></h2>
<p>Öffne die Datei <code><a href="aufgabe.py" style="color: inherit; text-decoration: underline;">aufgabe.py</a></code> und setze die folgenden Meilensteine um:</p>
<ul class="task-list">
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t19_1">
<label for="t19_1" class="task-label">
<strong>TODO 1 (Endloszähler):</strong> Implementiere <code>endlos_zaehler(start, schritt)</code> mit <code>while True</code> und <code>yield</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t19_2">
<label for="t19_2" class="task-label">
<strong>TODO 2 (Fibonacci Generator):</strong> Implementiere <code>fibonacci_generator(anzahl)</code> mit Validierung (keine negativen Zahlen) und Tupel-Update <code>a, b = b, a + b</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t19_3">
<label for="t19_3" class="task-label">
<strong>TODO 3 (Filter-Stream):</strong> Implementiere <code>filter_grosse_zahlen(zahlen_iterable, schwellenwert)</code>, der speicherschonend nur Werte <code>> schwellenwert</code> liefert.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t19_4">
<label for="t19_4" class="task-label">
<strong>TODO 4 (Generator Expression):</strong> Implementiere <code>erzeuge_quadrat_generator(n)</code> mit einer kompakten Generator-Expression <code>(x**2 for x in ...)</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t19_5">
<label for="t19_5" class="task-label">
<strong>TODO 5 (itertools.chain):</strong> Implementiere <code>kette_listen(*listen)</code> zum Zusammenführen beliebiger Iterables in eine flache Liste.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t19_6">
<label for="t19_6" class="task-label">
<strong>TODO 6 (itertools.permutations):</strong> Implementiere <code>erzeuge_passwort_kombinationen(zeichen, laenge)</code> mit <code>"".join()</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t19_7">
<label for="t19_7" class="task-label">
<strong>TODO 7 (itertools.cycle):</strong> Implementiere <code>zyklische_elemente(elemente, anzahl)</code> für periodische Taktgeber.
</label>
</li>
</ul>
<h3>💡 Lösungshinweise (Aufklappen bei Bedarf)</h3>
<details>
<summary>🔍 Tipp zu TODO 2: Fibonacci mit yield</summary>
<div class="details-content">
<p>Validiere zuerst die Eingabe und iteriere mit einer Zählschleife:</p>
<div class="code-container">
<pre><span class="kw">if</span> <span class="var">anzahl</span> &lt; <span class="num">0</span>:
<span class="kw">raise</span> <span class="fn">ValueError</span>(<span class="str">"Anzahl darf nicht negativ sein!"</span>)
<span class="var">a</span>, <span class="var">b</span> = <span class="num">0</span>, <span class="num">1</span>
<span class="kw">for</span> <span class="var">_</span> <span class="kw">in</span> <span class="fn">range</span>(<span class="var">anzahl</span>):
<span class="kw">yield</span> <span class="var">a</span>
<span class="var">a</span>, <span class="var">b</span> = <span class="var">b</span>, <span class="var">a</span> + <span class="var">b</span></pre>
</div>
</div>
</details>
<details>
<summary>🔍 Tipp zu TODO 6: Passwort-Permutationen als String-Liste</summary>
<div class="details-content">
<p><code>itertools.permutations</code> liefert Tupel von Zeichen. Mit <code>"".join()</code> machst du daraus Strings:</p>
<div class="code-container">
<pre><span class="kw">return</span> [<span class="str">""</span>.<span class="fn">join</span>(<span class="var">p</span>) <span class="kw">for</span> <span class="var">p</span> <span class="kw">in</span> <span class="var">itertools</span>.<span class="fn">permutations</span>(<span class="var">zeichen</span>, <span class="var">laenge</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 19_generatoren_und_itertools)</span>
</div>
<pre><code>python3 test_aufgabe.py</code></pre>
</div>
<h3>2. Benchmark-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="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="btn btn-secondary">&larr; Zurück zu Kapitel 16 (Master-Abschluss)</a>
<a href="../../index.html" class="btn btn-secondary">🏠 Kursübersicht</a>
<a href="../20_dataclasses_und_typehints/index.html" class="btn">Weiter zu Kapitel 20 (Dataclasses &amp; Types) &rarr;</a>
</div>
</section>
</main>
</div>
</div>
<button type="button" class="sidebar-mobile-toggle"><span>☰ Menü</span></button>
<div class="sidebar-overlay"></div>
<!-- Scripts -->
<script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script>
<script src="../../assets/interpreter.js"></script>
<script src="../../assets/certificate.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/runner.js"></script>
<script src="../../assets/ui_enhancements.js"></script>
<script src="../../assets/sidebar.js"></script>
</body>
</html>

View file

@ -0,0 +1,145 @@
"""
Kapitel 19: Generatoren, Iteratoren & itertools (Memory Efficiency & Streaming) 🌊⚡
==================================================================================
Musterlösung für speichereffiziente Datenverarbeitung, Generatoren und itertools.
"""
import sys
import itertools
from typing import Iterator, Iterable, Any, Union, List, Tuple
# ==============================================================================
# TEIL 1: GRUNDLAGEN VON ITERATOREN & GENERATOREN MIT YIELD
# ==============================================================================
def endlos_zaehler(start: int = 0, schritt: int = 1) -> Iterator[int]:
"""
Ein unendlicher Generator, der Zahlen beginnend bei 'start' mit der
Schrittweite 'schritt' generiert.
"""
aktuell = int(start)
schritt_wert = int(schritt)
while True:
yield aktuell
aktuell += schritt_wert
def fibonacci_generator(anzahl: int) -> Iterator[int]:
"""
Ein Generator, der die ersten 'anzahl' Fibonacci-Zahlen on-demand erzeugt.
"""
if anzahl < 0:
raise ValueError("Anzahl darf nicht negativ sein!")
a, b = 0, 1
for _ in range(anzahl):
yield a
a, b = b, a + b
# ==============================================================================
# TEIL 2: SPEICHERSCHONENDE FILTER & GENERATOR-EXPRESSIONS
# ==============================================================================
def filter_grosse_zahlen(zahlen_iterable: Iterable[Union[int, float]], schwellenwert: float) -> Iterator[Union[int, float]]:
"""
Ein Generator-Filter, der nur Zahlen liefert, die strikt größer als
der angegebene 'schwellenwert' sind (x > schwellenwert).
"""
for x in zahlen_iterable:
if x > schwellenwert:
yield x
def erzeuge_quadrat_generator(n: int) -> Iterator[int]:
"""
Erzeugt eine Generator-Expression für Quadratzahlen von 1 bis n (inklusive).
"""
if n < 0:
raise ValueError("n darf nicht negativ sein!")
return (x ** 2 for x in range(1, n + 1))
# ==============================================================================
# TEIL 3: DAS ITERTOOLS-MODUL (CHAIN, PERMUTATIONS & CYCLE)
# ==============================================================================
def kette_listen(*listen: Iterable[Any]) -> List[Any]:
"""
Verbindet beliebig viele Iterables (*args) mit 'itertools.chain' zu einer
einzigen flachen Liste.
"""
return list(itertools.chain(*listen))
def erzeuge_passwort_kombinationen(zeichen: str, laenge: int) -> List[str]:
"""
Erzeugt alle möglichen Permutationen der angegebenen Zeichenkette mit der
exakten Länge 'laenge' unter Verwendung von 'itertools.permutations'.
"""
if laenge < 0 or laenge > len(zeichen):
raise ValueError(f"Ungültige Länge {laenge} für Zeichenkette der Länge {len(zeichen)}!")
return ["".join(p) for p in itertools.permutations(zeichen, laenge)]
def zyklische_elemente(elemente: List[Any], anzahl: int) -> List[Any]:
"""
Nimmt eine Liste von Elementen und liefert die ersten 'anzahl' Elemente
in zyklischer Reihenfolge mit 'itertools.cycle'.
"""
if anzahl < 0:
raise ValueError("Anzahl darf nicht negativ sein!")
if anzahl == 0:
return []
if not elemente:
raise ValueError("Elemente-Liste darf nicht leer sein bei anzahl > 0!")
zyk = itertools.cycle(elemente)
return [next(zyk) for _ in range(anzahl)]
# ==============================================================================
# DEMONSTRATION & SPEICHER-BENCHMARK
# ==============================================================================
if __name__ == "__main__":
print("=" * 70)
print("🌊 KAPITEL 19: MUSTERLÖSUNG – GENERATOREN & ITERTOOLS")
print("=" * 70)
# 1. Speichervergleich: Liste vs. Generator
elemente_anzahl = 1_000_000
print(f"\n📊 SPEICHERVERGLEICH FÜR {elemente_anzahl:,} ZAHLEN:")
liste = [x for x in range(elemente_anzahl)]
gen = (x for x in range(elemente_anzahl))
speicher_liste = sys.getsizeof(liste) / (1024 * 1024)
speicher_gen = sys.getsizeof(gen)
print(f" 🔴 Liste (Badewanne): {speicher_liste:.2f} MB RAM im Hauptspeicher!")
print(f" 🟢 Generator (Wasserhahn): {speicher_gen} Bytes RAM (Faktor {int(speicher_liste * 1024 * 1024 / speicher_gen):,}x sparsamer!)")
# 2. Endloszähler Test
print("\n⏱️ Endloszähler (erste 5 Werte ab 100 in 10er Schritten):")
zaehler = endlos_zaehler(100, 10)
for _ in range(5):
print(f" -> {next(zaehler)}")
# 3. Fibonacci Test
print("\n🐰 Die ersten 10 Fibonacci-Zahlen:")
print(f" -> {list(fibonacci_generator(10))}")
# 4. Passwort-Permutationen Test
print("\n🔐 Passwort-Permutationen für 'abc' (Länge 2):")
print(f" -> {erzeuge_passwort_kombinationen('abc', 2)}")
# 5. Zyklischer Taktgeber
print("\n🚦 Ampel-Zyklus (7 Schritte):")
print(f" -> {zyklische_elemente(['Rot', 'Gelb', 'Gruen'], 7)}")
print("\n" + "=" * 70)
print("✅ Musterlösung erfolgreich ausgeführt!")
print("=" * 70)

View file

@ -0,0 +1,190 @@
import unittest
import sys
import types
import inspect
from pathlib import Path
# Sicherstellen, dass das aktuelle Verzeichnis im Python-Pfad liegt
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel19(unittest.TestCase):
"""
Testsuite für Kapitel 19: Generatoren, Iteratoren & itertools (Memory Efficiency).
"""
def setUp(self):
self.endlos_zaehler = getattr(aufgabe, "endlos_zaehler", None)
self.fibonacci_generator = getattr(aufgabe, "fibonacci_generator", None)
self.filter_grosse_zahlen = getattr(aufgabe, "filter_grosse_zahlen", None)
self.erzeuge_quadrat_generator = getattr(aufgabe, "erzeuge_quadrat_generator", None)
self.kette_listen = getattr(aufgabe, "kette_listen", None)
self.erzeuge_passwort_kombinationen = getattr(aufgabe, "erzeuge_passwort_kombinationen", None)
self.zyklische_elemente = getattr(aufgabe, "zyklische_elemente", None)
self.assertIsNotNone(self.endlos_zaehler, "Funktion 'endlos_zaehler' nicht gefunden!")
self.assertIsNotNone(self.fibonacci_generator, "Funktion 'fibonacci_generator' nicht gefunden!")
self.assertIsNotNone(self.filter_grosse_zahlen, "Funktion 'filter_grosse_zahlen' nicht gefunden!")
self.assertIsNotNone(self.erzeuge_quadrat_generator, "Funktion 'erzeuge_quadrat_generator' nicht gefunden!")
self.assertIsNotNone(self.kette_listen, "Funktion 'kette_listen' nicht gefunden!")
self.assertIsNotNone(self.erzeuge_passwort_kombinationen, "Funktion 'erzeuge_passwort_kombinationen' nicht gefunden!")
self.assertIsNotNone(self.zyklische_elemente, "Funktion 'zyklische_elemente' nicht gefunden!")
# ==========================================================================
# 1. Endloszähler (Generator mit yield)
# ==========================================================================
def test_01_endlos_zaehler(self):
"""Prüft, ob endlos_zaehler ein echter Generator ist und korrekt zählt."""
gen = self.endlos_zaehler(start=0, schritt=1)
self.assertTrue(inspect.isgenerator(gen) or isinstance(gen, types.GeneratorType),
"endlos_zaehler muss ein Generator sein (yield nutzen)!")
# Erste 5 Werte prüfen
werte = [next(gen) for _ in range(5)]
self.assertEqual(werte, [0, 1, 2, 3, 4])
# Mit benutzerdefiniertem Start und Schrittweite
gen2 = self.endlos_zaehler(start=100, schritt=5)
werte2 = [next(gen2) for _ in range(4)]
self.assertEqual(werte2, [100, 105, 110, 115])
# Mit negativer Schrittweite
gen3 = self.endlos_zaehler(start=10, schritt=-2)
werte3 = [next(gen3) for _ in range(4)]
self.assertEqual(werte3, [10, 8, 6, 4])
# ==========================================================================
# 2. Fibonacci-Generator
# ==========================================================================
def test_02_fibonacci_generator(self):
"""Prüft Fibonacci-Generator für verschiedene Anzahlen und Fehlerbehandlung."""
gen = self.fibonacci_generator(7)
self.assertTrue(inspect.isgenerator(gen) or isinstance(gen, types.GeneratorType),
"fibonacci_generator muss ein Generator sein!")
ergebnis = list(gen)
self.assertEqual(ergebnis, [0, 1, 1, 2, 3, 5, 8])
# Grenzfälle
self.assertEqual(list(self.fibonacci_generator(0)), [])
self.assertEqual(list(self.fibonacci_generator(1)), [0])
self.assertEqual(list(self.fibonacci_generator(2)), [0, 1])
self.assertEqual(list(self.fibonacci_generator(10)), [0, 1, 1, 2, 3, 5, 8, 13, 21, 34])
# Negative Anzahl muss ValueError werfen
with self.assertRaises(ValueError, msg="Negative Anzahl muss ValueError auslösen!"):
list(self.fibonacci_generator(-5))
# ==========================================================================
# 3. Speicherschonender Filter (filter_grosse_zahlen)
# ==========================================================================
def test_03_filter_grosse_zahlen(self):
"""Prüft, ob der Filter Zahlen speicherschonend per Generator filtert."""
eingabe = [10, 2.5, 50, 100, 5, 20.1]
gen = self.filter_grosse_zahlen(eingabe, 20.0)
self.assertTrue(inspect.isgenerator(gen) or isinstance(gen, types.GeneratorType),
"filter_grosse_zahlen muss ein Generator sein (yield nutzen)!")
ergebnis = list(gen)
self.assertEqual(ergebnis, [50, 100, 20.1])
# Keine Treffer
self.assertEqual(list(self.filter_grosse_zahlen([1, 2, 3], 10)), [])
# Alle Treffer
self.assertEqual(list(self.filter_grosse_zahlen([15, 20, 30], 10)), [15, 20, 30])
# Funktioniert auch mit einem Generator als Eingabe (Streaming-Pipeline)
eingabe_gen = (x * 10 for x in range(5)) # 0, 10, 20, 30, 40
gefiltert = list(self.filter_grosse_zahlen(eingabe_gen, 15))
self.assertEqual(gefiltert, [20, 30, 40])
# ==========================================================================
# 4. Generator Expression (erzeuge_quadrat_generator)
# ==========================================================================
def test_04_erzeuge_quadrat_generator(self):
"""Prüft die Erzeugung von Generator Expressions."""
gen = self.erzeuge_quadrat_generator(5)
self.assertTrue(inspect.isgenerator(gen) or isinstance(gen, types.GeneratorType),
"erzeuge_quadrat_generator muss eine Generator-Expression zurückgeben!")
self.assertEqual(list(gen), [1, 4, 9, 16, 25])
self.assertEqual(list(self.erzeuge_quadrat_generator(0)), [])
with self.assertRaises(ValueError, msg="Negatives n muss ValueError auslösen!"):
self.erzeuge_quadrat_generator(-3)
# ==========================================================================
# 5. itertools.chain (kette_listen)
# ==========================================================================
def test_05_kette_listen(self):
"""Prüft das Verketten beliebiger Iterables mit itertools.chain."""
l1 = [1, 2]
l2 = [3, 4, 5]
l3 = [6]
self.assertEqual(self.kette_listen(l1, l2, l3), [1, 2, 3, 4, 5, 6])
# Leere Iterables
self.assertEqual(self.kette_listen([], [42], []), [42])
self.assertEqual(self.kette_listen(), [])
# Gemischte Iterables (Tupel, Sets, Generatoren)
t = (10, 20)
g = (x for x in [30, 40])
self.assertEqual(self.kette_listen(t, g), [10, 20, 30, 40])
# ==========================================================================
# 6. itertools.permutations (erzeuge_passwort_kombinationen)
# ==========================================================================
def test_06_erzeuge_passwort_kombinationen(self):
"""Prüft die Erzeugung von Permutationen mit itertools.permutations."""
kombis = self.erzeuge_passwort_kombinationen("abc", 2)
erwartet = ['ab', 'ac', 'ba', 'bc', 'ca', 'cb']
self.assertEqual(sorted(kombis), sorted(erwartet))
self.assertEqual(len(kombis), 6)
# Einzelzeichen
kombis1 = self.erzeuge_passwort_kombinationen("xyz", 1)
self.assertEqual(sorted(kombis1), ['x', 'y', 'z'])
# Gesamtlänge
kombis_voll = self.erzeuge_passwort_kombinationen("ab", 2)
self.assertEqual(sorted(kombis_voll), ['ab', 'ba'])
# Länge 0
self.assertEqual(self.erzeuge_passwort_kombinationen("abc", 0), [''])
# Fehlerbehandlung bei ungültigen Längen
with self.assertRaises(ValueError, msg="Negative Länge muss ValueError werfen"):
self.erzeuge_passwort_kombinationen("abc", -1)
with self.assertRaises(ValueError, msg="Länge > Zeichenanzahl muss ValueError werfen"):
self.erzeuge_passwort_kombinationen("ab", 5)
# ==========================================================================
# 7. itertools.cycle (zyklische_elemente)
# ==========================================================================
def test_07_zyklische_elemente(self):
"""Prüft zyklische Entnahme mit itertools.cycle."""
farben = ["Rot", "Gelb", "Gruen"]
res = self.zyklische_elemente(farben, 7)
self.assertEqual(res, ["Rot", "Gelb", "Gruen", "Rot", "Gelb", "Gruen", "Rot"])
# Anzahl 0
self.assertEqual(self.zyklische_elemente(farben, 0), [])
# Fehlerbehandlung
with self.assertRaises(ValueError, msg="Negative Anzahl muss ValueError werfen"):
self.zyklische_elemente(farben, -2)
with self.assertRaises(ValueError, msg="Leere Liste bei anzahl > 0 muss ValueError werfen"):
self.zyklische_elemente([], 5)
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,141 @@
# Kapitel 20: Dataclasses, Type Hints & Decorators (Moderne Python-Architektur) 🏷️✨
Willkommen zu **Kapitel 20** im Lehrpfad 4 (*Professional Data Engineering*)!
In diesem Modul lernst du die Kernbausteine moderner, robuster Python-Architekturen kennen:
1. **Dataclasses (`@dataclass`)**: Schluss mit endlosem Boilerplate-Code für `__init__`, `__repr__` und `__eq__`!
2. **Fortgeschrittene Type Hints**: Statische Typsicherheit mit `Optional`, `Union`, `Callable` und `Dict`.
3. **Eigene Decorators**: Funktionserweiterungen wie Logging, Zeitmessung und Zugriffskontrollen als wiederverwendbare Hüllen.
4. **Standard-Decorators**: `@property`, `@classmethod` und `@staticmethod`.
---
## 🧭 Didaktische Analogien
### A) Das Fertighaus / Die Gussform (`@dataclass`)
Statt jeden Ziegelstein für den Konstruktor (`def __init__(self, a, b, c): self.a = a...`), den String-Druck (`def __repr__`) und Vergleiche (`def __eq__`) von Hand zu mauern, gibst du Python einfach die **Gussform** (die Feld-Typen). Python gießt das gesamte Klassenfundament automatisch in Millisekunden!
### B) Die Geschenkverpackung / Schutzhülle (Decorators)
Ein Decorator nimmt eine existierende Funktion, wickelt eine schützende Hülle (den Wrapper) darum und gibt das eingepackte Geschenk zurück. Die ursprüngliche Funktion bleibt unverändert, erhält aber Superkräfte (z.B. Zeitmessung oder Aufrufzählung)!
```
┌────────────────────────────────────────────────────────┐
│ 🎁 DECORATOR WRAPPER │
│ │
│ [1. Vorher] ⏱️ Startzeit erfassen / Zähler +1 │
│ [2. Kern] ▶️ Originalfunktion ausführen │
│ [3. Nachher] 📊 Dauer berechnen & loggen │
│ │
└────────────────────────────────────────────────────────┘
```
---
## 📦 1. Dataclasses: Schluss mit Boilerplate-Code
### Vor Python 3.7 (Klassischer Boilerplate):
```python
class ArtikelKlassisch:
def __init__(self, name: str, preis: float, kategorie: str = "Allgemein"):
self.name = name
self.preis = preis
self.kategorie = kategorie
def __repr__(self):
return f"ArtikelKlassisch(name='{self.name}', preis={self.preis}, kategorie='{self.kategorie}')"
def __eq__(self, other):
if not isinstance(other, ArtikelKlassisch):
return False
return (self.name, self.preis, self.kategorie) == (other.name, other.preis, other.kategorie)
```
### Modern mit `@dataclass`:
```python
from dataclasses import dataclass, field
@dataclass
class Artikel:
name: str
preis: float
kategorie: str = "Allgemein"
```
Python erzeugt `__init__`, `__repr__` und `__eq__` vollautomatisch!
### ⚠️ Wichtig: Veränderliche Standardwerte mit `field(default_factory=...)`
Listen oder Dictionaries dürfen in Dataclasses niemals als `artikel: list = []` definiert werden (da alle Instanzen sonst dieselbe Liste teilen würden). Nutze stattdessen immer `field(default_factory=list)`:
```python
@dataclass
class Warenkorb:
kunde: str
artikel_liste: list[Artikel] = field(default_factory=list)
```
---
## 🏷️ 2. Fortgeschrittene Type Hints
Pythons `typing`-Modul macht deinen Code selbsterklärend und schützt vor bösen Überraschungen:
| Type Hint | Bedeutung | Beispiel |
| :--- | :--- | :--- |
| `Optional[T]` | Wert vom Typ `T` oder `None` (`Union[T, None]`) | `Optional[str]` (z.B. `"Max"` oder `None`) |
| `Union[A, B]` | Wert kann entweder Typ `A` oder Typ `B` sein | `Union[int, float]` |
| `Callable[[Arg1, Arg2], ReturnType]` | Eine Funktion als Argument oder Rückgabe | `Callable[[int, int], bool]` |
| `List[T]`, `Dict[K, V]` | Typisierte Listen und Key-Value-Mappings | `Dict[str, Union[str, int]]` |
---
## 🎨 3. Eigene Decorators schreiben
Ein Decorator ist eine Funktion höherer Ordnung, die eine Funktion entgegennimmt und eine neue Wrapper-Funktion zurückliefert. Nutze immer `@functools.wraps(func)`, um den ursprünglichen Namen und Docstring zu erhalten:
```python
import functools
import time
def zeitmessung(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
ergebnis = func(*args, **kwargs)
dauer = time.perf_counter() - start
print(f"[⏱️] {func.__name__} dauerte {dauer:.4f}s")
return ergebnis
return wrapper
@zeitmessung
def berechne_daten():
time.sleep(0.1)
return 42
```
---
## ⚙️ 4. Standard-Decorators im Überblick
- **`@property`**: Ermöglicht den Aufruf einer Methode wie ein Attribut (`auto.geschwindigkeit` statt `auto.get_geschwindigkeit()`).
- **`@classmethod`**: Erhält die Klasse `cls` als ersten Parameter; ideal für alternative Konstruktoren (Factory Methods wie `Artikel.aus_csv_zeile(zeile)`).
- **`@staticmethod`**: Reine Hilfsfunktion innerhalb des Klassen-Namespaces ohne Zugriff auf `self` oder `cls`.
---
## 📁 Die Dateien in diesem Ordner
- **`index.html`**: Interaktive Web-Lernseite mit visuellen Ablaufdiagrammen, Vorher-Nachher-Vergleichen, Cheat-Sheets und Checkliste.
- **`aufgabe.py`**: Dein Arbeitsblatt mit Type Hints und didaktischen TODOs für Dataclasses, Warenkorb, Decorators und Typprüfung.
- **`test_aufgabe.py`**: Automatische Unittest-Suite zur Überprüfung aller Anforderungen.
- **`musterloesung.py`**: Vollständig ausprogrammierte Referenzlösung.
---
## 🚀 Schnellstart
```bash
# 1. Bearbeite aufgabe.py
# 2. Führe die automatischen Tests aus:
python3 test_aufgabe.py
# 3. Oder starte das interaktive Skript:
python3 aufgabe.py
```

View file

@ -0,0 +1,286 @@
"""
Kapitel 20: Dataclasses, Type Hints & Decorators (Moderne Python-Architektur) 🏷️✨
==================================================================================
Lerne moderne Python-Architekturmuster für typsicheren, sauberen Code:
1. @dataclass (Automatische Konstruktoren, __repr__, __eq__, field(default_factory=...))
2. Fortgeschrittene Type Hints (Optional, Union, Callable, Dict, List)
3. Eigene Decorators schreiben (@functools.wraps, Wrapper-Funktionen)
4. Standard-Decorators (@property, @classmethod, @staticmethod)
Didaktische Analogie:
- "Gussform / Fertighaus" (@dataclass): Spart seitenlangen Boilerplate-Code für __init__, __repr__ etc.
- "Geschenkverpackung / Schutzhülle" (Decorator): Erweitert Funktionen um Logging, Zähler oder Zeitmessung, ohne ihren Code zu ändern.
"""
import time
import functools
from dataclasses import dataclass, field
from typing import Optional, Union, Callable, List, Dict, Any
# ==============================================================================
# TEIL 1: DATACLASSES FÜR TYPSICHERE DATENMODELLE
# ==============================================================================
@dataclass
class Artikel:
"""
Ein Artikel im Online-Shop als moderne Dataclass.
Attribute:
name (str): Artikelbezeichnung (darf nicht leer sein).
preis (float): Nettopreis in Euro (muss >= 0 sein).
kategorie (str): Warengruppe (Standard: 'Allgemein').
"""
name: str
preis: float
kategorie: str = "Allgemein"
def __post_init__(self) -> None:
"""
Wird automatisch direkt nach dem generierten __init__ aufgerufen.
Validiere hier die Daten!
"""
# ======================================================================
# 🎯 TEILZIEL 1 (TODO 1): Validierung in __post_init__
# 1. Wenn name leer oder nur Leerzeichen ist, löse ValueError aus.
# 2. Wenn preis < 0 ist, löse ValueError aus.
# ======================================================================
pass
def berechne_bruttopreis(self, mwst_satz: float = 0.19) -> float:
"""
Berechnet den Bruttopreis inklusive Mehrwertsteuer, gerundet auf 2 Nachkommastellen.
Parameter:
mwst_satz (float): Mehrwertsteuersatz als Dezimalzahl (z.B. 0.19 für 19%).
Exceptions:
ValueError: Falls mwst_satz < 0 ist.
Beispiele:
Artikel("Buch", 10.0).berechne_bruttopreis(0.07) -> 10.7
Artikel("Maus", 20.0).berechne_bruttopreis(0.19) -> 23.8
"""
# ======================================================================
# 🎯 TEILZIEL 2 (TODO 2): Implementiere berechne_bruttopreis
# 1. Validiere: mwst_satz darf nicht negativ (< 0) sein.
# 2. Berechne: preis * (1.0 + mwst_satz)
# 3. Runde auf 2 Dezimalstellen mit round(..., 2) und gib den Wert zurück.
# ======================================================================
pass
@classmethod
def aus_csv_zeile(cls, csv_zeile: str) -> "Artikel":
"""
Erstellt ein Artikel-Objekt aus einer CSV-Zeile wie 'Laptop;999.99;Elektronik'.
"""
# ======================================================================
# 🎯 TEILZIEL 3 (TODO 3): Implementiere die @classmethod aus_csv_zeile
# 1. Trenne die csv_zeile an Semikolons (csv_zeile.split(';')).
# 2. Entpacke name, preis (als float) und optional kategorie.
# 3. Erzeuge und gib eine neue Instanz mit cls(...) zurück.
# ======================================================================
pass
@staticmethod
def ist_gueltiger_preis(preis: float) -> bool:
"""
Prüft statisch, ob ein Preis gültig (numerisch und >= 0) ist.
"""
# ======================================================================
# 🎯 TEILZIEL 4 (TODO 4): Implementiere die @staticmethod ist_gueltiger_preis
# 1. Gib True zurück, wenn preis ein int oder float ist und preis >= 0.0.
# ======================================================================
pass
@dataclass
class Warenkorb:
"""
Ein digitaler Warenkorb, der Artikel sammelt und verwaltet.
Attribute:
kunde (str): Name des Kunden.
artikel_liste (list[Artikel]): Liste der Artikel (nutze field(default_factory=list)!).
"""
kunde: str
artikel_liste: List[Artikel] = field(default_factory=list)
def artikel_hinzufuegen(self, *artikel: Artikel) -> None:
"""
Fügt beliebig viele Artikel (*args) zum Warenkorb hinzu.
Exceptions:
TypeError: Falls ein Element kein Artikel-Objekt ist.
"""
# ======================================================================
# 🎯 TEILZIEL 5 (TODO 5): Implementiere artikel_hinzufuegen
# 1. Iteriere über alle übergebenen Artikel.
# 2. Prüfe mit isinstance(a, Artikel), ob der Typ stimmt (sonst TypeError).
# 3. Hänge das Element an self.artikel_liste an.
# ======================================================================
pass
def artikel_entfernen(self, artikel_name: str) -> bool:
"""
Entfernt den ersten Artikel mit dem passenden Namen (Groß-/Kleinschreibung ignorieren).
Gibt True zurück, wenn der Artikel gefunden und entfernt wurde, sonst False.
"""
# ======================================================================
# 🎯 TEILZIEL 6 (TODO 6): Implementiere artikel_entfernen
# 1. Durchsuche self.artikel_liste nach einem Artikel mit a.name.lower() == artikel_name.lower().
# 2. Entferne ihn mit self.artikel_liste.remove(a) und gib True zurück.
# 3. Wenn nicht gefunden, gib False zurück.
# ======================================================================
pass
def gesamtsumme(self, brutto: bool = False, mwst_satz: float = 0.19) -> float:
"""
Berechnet die Gesamtsumme aller Artikel im Warenkorb.
Parameter:
brutto (bool): Wenn True, wird der Bruttopreis mit mwst_satz summiert.
mwst_satz (float): Der MwSt-Satz bei Bruttoberechnung.
Rückgabe:
float: Die auf 2 Stellen gerundete Gesamtsumme.
"""
# ======================================================================
# 🎯 TEILZIEL 7 (TODO 7): Implementiere gesamtsumme
# 1. Wenn brutto == True: Summiere a.berechne_bruttopreis(mwst_satz) für alle Artikel.
# 2. Wenn brutto == False: Summiere a.preis für alle Artikel.
# 3. Runde auf 2 Nachkommastellen und gib den Wert zurück (bei leerem Korb 0.0).
# ======================================================================
pass
def __len__(self) -> int:
"""Gibt die Anzahl der Artikel im Warenkorb zurück."""
return len(self.artikel_liste)
# ==============================================================================
# TEIL 2: EIGENE DECORATORS SCHREIBEN
# ==============================================================================
def aufruf_zaehler(func: Callable) -> Callable:
"""
Ein Decorator, der zählt, wie oft eine dekorierte Funktion aufgerufen wurde.
Der Zähler wird als Attribut 'aufrufe' an der Wrapper-Funktion hinterlegt.
Beispiel:
@aufruf_zaehler
def gruss(name):
return f"Hallo {name}"
gruss("Anna")
gruss.aufrufe # -> 1
gruss("Ben")
gruss.aufrufe # -> 2
"""
# ==========================================================================
# 🎯 TEILZIEL 8 (TODO 8): Implementiere den Decorator aufruf_zaehler
# 1. Definiere die innere Funktion wrapper(*args, **kwargs).
# 2. Nutze @functools.wraps(func) über der wrapper-Funktion.
# 3. Erhöhe wrapper.aufrufe um 1.
# 4. Führe func(*args, **kwargs) aus und gib das Ergebnis zurück.
# 5. Initialisiere wrapper.aufrufe = 0 vor der Rückgabe von wrapper.
# ==========================================================================
pass
def zeitmessung(func: Callable) -> Callable:
"""
Ein Decorator, der die Ausführungszeit einer Funktion misst und die Dauer
als Attribut 'letzte_dauer' (in Sekunden) an der Wrapper-Funktion speichert.
"""
# ==========================================================================
# 🎯 TEILZIEL 9 (TODO 9): Implementiere den Decorator zeitmessung
# 1. Definiere wrapper(*args, **kwargs) mit @functools.wraps(func).
# 2. Miss die Startzeit mit time.perf_counter().
# 3. Führe das Original func(*args, **kwargs) aus.
# 4. Berechne dauer = time.perf_counter() - start_zeit.
# 5. Speichere wrapper.letzte_dauer = dauer.
# 6. Gib das Funktionsergebnis zurück.
# 7. Initialisiere wrapper.letzte_dauer = 0.0 vor der Rückgabe von wrapper.
# ==========================================================================
pass
# ==============================================================================
# TEIL 3: FORTGESCHRITTENE TYPE HINTS (UNION, DICT, TYPED DATA)
# ==============================================================================
def formatiere_benutzer(daten: Dict[str, Union[str, int, bool]]) -> str:
"""
Formatiert ein Benutzer-Dictionary mit verschiedenen Datentypen zu einem
lesbaren Status-String.
Erwartete Schlüssel im Dict:
- "username" (str, Pflicht): Der Benutzername.
- "alter" (int, optional): Alter in Jahren.
- "ist_admin" (bool, optional): Ob der Benutzer Administratorrechte besitzt.
Rückgabe:
str: Formatierter String, z.B.
"Benutzer: Alice | Alter: 28 | Rolle: Administrator" oder
"Benutzer: Bob | Alter: Unbekannt | Rolle: Standardbenutzer"
Exceptions:
KeyError: Falls der Pflichtschlüssel "username" fehlt.
ValueError: Falls "username" ein leerer String ist.
"""
# ==========================================================================
# 🎯 TEILZIEL 10 (TODO 10): Implementiere formatiere_benutzer
# 1. Prüfe, ob "username" in daten enthalten ist (wenn nicht -> KeyError).
# 2. Prüfe, ob daten["username"] ein nicht-leerer String ist (wenn nicht -> ValueError).
# 3. Lese das Alter: Wenn vorhanden, nutze den Wert, sonst "Unbekannt".
# 4. Lese "ist_admin": Wenn True, rolle = "Administrator", sonst "Standardbenutzer".
# 5. Gib den String im Format:
# "Benutzer: {username} | Alter: {alter} | Rolle: {rolle}" zurück.
# ==========================================================================
pass
# ==============================================================================
# DEMONSTRATION & MAIN
# ==============================================================================
if __name__ == "__main__":
print("=" * 70)
print("🏷️ KAPITEL 20: DATACLASSES, TYPE HINTS & DECORATORS")
print("=" * 70)
# 1. Dataclass Artikel testen
laptop = Artikel(name="ThinkPad X1", preis=1499.00, kategorie="Elektronik")
print(f"\n📦 Artikel Dataclass:\n {laptop}")
print(f" Bruttopreis (19%): {laptop.berechne_bruttopreis(0.19):.2f} €")
# 2. Warenkorb testen
korb = Warenkorb(kunde="Max Mustermann")
korb.artikel_hinzufuegen(
laptop,
Artikel("USB-C Hub", 39.90, "Zubehoer"),
Artikel("Kaffeebecher", 12.50, "Haushalt")
)
print(f"\n🛒 Warenkorb für {korb.kunde} ({len(korb)} Artikel):")
print(f" Nettosumme: {korb.gesamtsumme():.2f} €")
print(f" Bruttosumme: {korb.gesamtsumme(brutto=True):.2f} €")
# 3. Aufruf-Zähler Decorator
@aufruf_zaehler
def sage_hallo(name: str) -> str:
return f"Hallo, {name}!"
print("\n🔢 Aufruf-Zähler Decorator Test:")
print(sage_hallo("Alice"))
print(sage_hallo("Bob"))
print(f" Anzahl Aufrufe: {sage_hallo.aufrufe}")
# 4. Typsicherer Benutzer-Formatierer
user = {"username": "CoderGirl99", "alter": 24, "ist_admin": True}
print(f"\n👤 Benutzerdaten:\n {formatiere_benutzer(user)}")
print("\n" + "=" * 70)
print("💡 Starte die Tests mit: python3 test_aufgabe.py")
print("=" * 70)

View file

@ -0,0 +1,478 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapitel 20: Dataclasses, Type Hints & Decorators</title>
<link rel="stylesheet" href="../../assets/style.css">
</head>
<body>
<div class="app-layout">
<aside class="app-sidebar">
<div class="sidebar-header">
<a href="../../index.html" class="sidebar-brand-link">
<div class="sidebar-logo">🐍</div>
<div>
<div class="sidebar-title">Python Portal</div>
<div class="sidebar-subtitle">Lernplattform</div>
</div>
</a>
</div>
<div class="sidebar-quick-nav">
<a href="../../index.html" class="sidebar-link">🏠 Startseite</a>
<a href="../../index.html#tutorial" class="sidebar-link">📖 Tutorial &amp; Workflow</a>
<a href="../../index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a>
</div>
<div class="sidebar-nav">
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🌱 1. Grundlagen</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">G01–G10</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_1_grundlagen/01_erste_schritte_taschenrechner/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧮</span>
<span class="sidebar-link-text">G01. Taschenrechner</span>
</a>
<a href="../../lehrpfad_1_grundlagen/02_variablen_und_datentypen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📦</span>
<span class="sidebar-link-text">G02. Variablen & Typen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/03_ein_und_ausgabe/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💬</span>
<span class="sidebar-link-text">G03. Ein- & Ausgabe</span>
</a>
<a href="../../lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚦</span>
<span class="sidebar-link-text">G04. Bedingungen (if)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎡</span>
<span class="sidebar-link-text">G05. Schleifen (for/while)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/06_funktionen_und_module/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🪄</span>
<span class="sidebar-link-text">G06. Funktionen & Module</span>
</a>
<a href="../../lehrpfad_1_grundlagen/07_listen_und_sequenzen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📋</span>
<span class="sidebar-link-text">G07. Listen & Sequenzen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/08_textverarbeitung_und_strings/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔤</span>
<span class="sidebar-link-text">G08. String-Methoden</span>
</a>
<a href="../../lehrpfad_1_grundlagen/09_dictionaries_und_sets/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗃️</span>
<span class="sidebar-link-text">G09. Dicts & Sets</span>
</a>
<a href="../../lehrpfad_1_grundlagen/10_comprehensions_datum_algorithmen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚀</span>
<span class="sidebar-link-text">G10. Comprehensions</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🏗️ 2. OOP Einstieg</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">00–06</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">00. Fehlersuche & Debug</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/01_einstieg_klassen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏗️</span>
<span class="sidebar-link-text">01. Einstieg in Klassen</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/02_init_und_self/index.html" class="sidebar-link">
<span class="sidebar-link-icon">⚙️</span>
<span class="sidebar-link-text">02. Konstruktor & self</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/03_methoden_und_verhalten/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏎️</span>
<span class="sidebar-link-text">03. Methoden & Verhalten</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04_str_und_darstellung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧾</span>
<span class="sidebar-link-text">04. __str__ Darstellung</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04b_umstieg_vscode/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💻</span>
<span class="sidebar-link-text">04b. VS Code Setup</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04c_git_und_versionskontrolle/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌿</span>
<span class="sidebar-link-text">04c. Git-Versionskontrolle</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/05_objekte_kombinieren/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎵</span>
<span class="sidebar-link-text">05. Objekte kombinieren</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🥚</span>
<span class="sidebar-link-text">06. Projekt: Tamagotchi</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🚀 3. Fortgeschritten</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">07–16</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_3_fortgeschrittenes_oop/07_referenzen_und_speicher/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧠</span>
<span class="sidebar-link-text">07. Referenzen & Speicher</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/08_operator_overloading_dunder/index.html" class="sidebar-link">
<span class="sidebar-link-icon">➕</span>
<span class="sidebar-link-text">08. Operator Overloading</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧪</span>
<span class="sidebar-link-text">09. Unit Tests & TDD</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧬</span>
<span class="sidebar-link-text">10. Vererbung & super()</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/11_polymorphie_und_interfaces/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎭</span>
<span class="sidebar-link-text">11. Polymorphie</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/12_exceptions_und_fehlerbehandlung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🛡️</span>
<span class="sidebar-link-text">12. Exceptions & Fehler</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/13_persistenz_json_und_csv/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💾</span>
<span class="sidebar-link-text">13. Persistenz (JSON/CSV)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/14_gui_mit_tkinter/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🖥️</span>
<span class="sidebar-link-text">14. Desktop-GUIs (Tkinter)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/15_parameter_und_container/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎒</span>
<span class="sidebar-link-text">15. Container & *args</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏆</span>
<span class="sidebar-link-text">16. Master-Abschlussprojekt</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>⚡ 4. Data Engineering</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">19–20</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../19_generatoren_und_itertools/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌊</span>
<span class="sidebar-link-text">19. Generatoren & itertools</span>
</a>
<a href="index.html" class="sidebar-link active">
<span class="sidebar-link-icon">🏷️</span>
<span class="sidebar-link-text">20. Dataclasses & Types</span>
</a>
</div>
</div>
</div>
<div class="sidebar-footer">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="font-size: 0.85rem; font-weight: 700; color: white;" id="sidebar-level-text">⭐ Level 1</div>
<div style="font-size: 0.82rem; font-weight: 700; color: #38bdf8;" id="sidebar-xp-text">0 XP</div>
</div>
</div>
</aside>
<div class="app-main">
<header>
<div class="header-container">
<nav class="nav-links">
<a href="../../index.html">🏠 Zur Kursübersicht</a>
<span>&rsaquo;</span>
<a href="../19_generatoren_und_itertools/index.html">Kapitel 19</a>
<span>&rsaquo;</span>
<span style="color: white; font-weight: 600;">Kapitel 20</span>
</nav>
<div class="header-badge">Lehrpfad 4 &bull; Professional Data Engineering</div>
<h1>Dataclasses, Type Hints &amp; Decorators 🏷️✨</h1>
<p>Schreibe eleganten, selbstdokumentierenden und typsicheren Code mit <code>@dataclass</code>, modernen Type Hints und maßgeschneiderten Decorators.</p>
</div>
</header>
<main class="container">
<!-- 📂 Schnellzugriff & Download aller Dateien dieser Einheit -->
<div style="background: #ffffff; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 14px 20px; margin-bottom: 25px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; box-shadow: var(--shadow-sm);">
<div style="font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 8px;">
<span>📂 Dateien dieser Einheit:</span>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="aufgabe.py" class="btn" style="padding: 6px 14px; font-size: 0.88rem; background: var(--primary);" download>📄 aufgabe.py (Startercode)</a>
<a href="test_aufgabe.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>🧪 test_aufgabe.py (Tests)</a>
<a href="musterloesung.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>💡 musterloesung.py (Lösung)</a>
<a href="README.md" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;">📖 README.md</a>
</div>
</div>
<!-- 1. Dataclasses: Das Fertighaus für Klassen -->
<section class="card">
<h2>1. @dataclass: Die Fertighaus-Gussform 🏗️📦</h2>
<p>
In vielen Programmen dienen Klassen primär als Datenbehälter. Bisher musstest du dafür seitenweise denselben Standard-Code schreiben:
</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin: 20px 0;">
<div style="background: var(--bg-subtle); border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 16px;">
<h3 style="color: var(--rose); margin-top: 0;">🔴 Klassisch (Viel Tipparbeit)</h3>
<div class="code-container" style="margin: 8px 0;">
<pre><span class="kw">class</span> <span class="fn">Artikel</span>:
<span class="kw">def</span> <span class="fn">__init__</span>(<span class="var">self</span>, <span class="var">name</span>, <span class="var">preis</span>):
<span class="var">self</span>.<span class="attr">name</span> = <span class="var">name</span>
<span class="var">self</span>.<span class="attr">preis</span> = <span class="var">preis</span>
<span class="kw">def</span> <span class="fn">__repr__</span>(<span class="var">self</span>):
<span class="kw">return</span> <span class="str">f"Artikel({self.name}, {self.preis})"</span>
<span class="kw">def</span> <span class="fn">__eq__</span>(<span class="var">self</span>, <span class="var">other</span>):
<span class="kw">return</span> (<span class="var">self</span>.<span class="attr">name</span>, <span class="var">self</span>.<span class="attr">preis</span>) == (<span class="var">other</span>.<span class="attr">name</span>, <span class="var">other</span>.<span class="attr">preis</span>)</pre>
</div>
</div>
<div style="background: var(--emerald-light); border: 2px solid var(--emerald); border-radius: var(--radius-md); padding: 16px;">
<h3 style="color: var(--emerald); margin-top: 0;">🟢 Modern mit @dataclass</h3>
<div class="code-container" style="margin: 8px 0;">
<pre><span class="kw">from</span> dataclasses <span class="kw">import</span> dataclass, field
<span class="kw">@dataclass</span>
<span class="kw">class</span> <span class="fn">Artikel</span>:
<span class="attr">name</span>: <span class="var">str</span>
<span class="attr">preis</span>: <span class="var">float</span>
<span class="attr">kategorie</span>: <span class="var">str</span> = <span class="str">"Allgemein"</span></pre>
</div>
<span style="font-weight: 700; color: #064e3b; font-size: 0.85rem;">✨ __init__, __repr__ und __eq__ sind automatisch fertig!</span>
</div>
</div>
<div class="box box-warning">
<div class="box-title">⚠️ Wichtig: Veränderliche Default-Listen mit field(default_factory=list)</div>
<p>Schreibe in einer Dataclass niemals <code>liste: list = []</code>, da sonst alle Instanzen dieselbe Liste teilen! Nutze stattdessen immer <code>field(default_factory=list)</code>.</p>
</div>
</section>
<!-- 2. Type Hints Cheat-Sheet -->
<section class="card">
<h2>2. Moderne Type Hints im Überblick 🏷️</h2>
<p>
Type Hints machen deinen Code selbsterklärend und ermöglichen IDEs wie VS Code blitzschnelle Autovervollständigung und Fehlerwarnungen:
</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);">Type Hint</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Bedeutung</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Praxis-Beispiel</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>Optional[str]</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Kann <code>str</code> oder <code>None</code> sein</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>def suche(id: int) -&gt; Optional[User]: ...</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>Union[int, float]</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Zahl kann Ganzzahl oder Fließkomma sein</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>def runden(wert: Union[int, float]) -&gt; float: ...</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>Callable[[A, B], C]</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Eine übergebene Funktion</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>def ausfuehren(func: Callable) -&gt; None: ...</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>Dict[str, Any]</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Schlüssel sind Strings, Werte beliebig</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>user_config: Dict[str, Any] = {"theme": "dark"}</code></td>
</tr>
</tbody>
</table>
</section>
<!-- 3. Eigene Decorators schreiben -->
<section class="card">
<h2>3. Eigene Decorators schreiben: Das Geschenkverpackungs-Muster 🎁</h2>
<p>
Ein <strong>Decorator</strong> ist eine Funktion, die eine andere Funktion einpackt und ihr zusätzliche Fähigkeiten verleiht (z.B. Zeitmessung, Logging, Aufrufzählung):
</p>
<div class="code-container">
<div class="code-header"><span>Eigener Zeitmessungs-Decorator mit @functools.wraps</span></div>
<pre><span class="kw">import</span> time
<span class="kw">import</span> functools
<span class="kw">def</span> <span class="fn">zeitmessung</span>(<span class="var">func</span>):
<span class="kw">@functools.wraps</span>(<span class="var">func</span>) <span class="cm"># Erhält Name und Docstring der Originalfunktion</span>
<span class="kw">def</span> <span class="fn">wrapper</span>(*<span class="var">args</span>, **<span class="var">kwargs</span>):
<span class="var">start</span> = time.<span class="fn">perf_counter</span>()
<span class="var">ergebnis</span> = <span class="var">func</span>(*<span class="var">args</span>, **<span class="var">kwargs</span>)
<span class="var">dauer</span> = time.<span class="fn">perf_counter</span>() - <span class="var">start</span>
<span class="fn">print</span>(<span class="str">f"[⏱️] {func.__name__} brauchte {dauer:.6f}s"</span>)
<span class="kw">return</span> <span class="var">ergebnis</span>
<span class="kw">return</span> <span class="var">wrapper</span>
<span class="kw">@zeitmessung</span>
<span class="kw">def</span> <span class="fn">schwere_berechnung</span>():
<span class="kw">return</span> <span class="fn">sum</span>(<span class="var">x</span>**<span class="num">2</span> <span class="kw">for</span> <span class="var">x</span> <span class="kw">in</span> <span class="fn">range</span>(<span class="num">500_000</span>))</pre>
</div>
</section>
<!-- 4. Aufgaben & Checkliste -->
<section class="card">
<h2>4. 🎯 Deine Aufgaben in <code>aufgabe.py</code></h2>
<p>Öffne die Datei <code><a href="aufgabe.py" style="color: inherit; text-decoration: underline;">aufgabe.py</a></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="t20_1">
<label for="t20_1" class="task-label">
<strong>TODO 1 &amp; 2 (Dataclass Artikel):</strong> Erstelle <code>__post_init__</code> zur Validierung von Preis (>= 0) und Name (nicht leer), sowie <code>berechne_bruttopreis(mwst_satz)</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t20_2">
<label for="t20_2" class="task-label">
<strong>TODO 3 &amp; 4 (Class- &amp; Staticmethod):</strong> Implementiere <code>aus_csv_zeile(csv_zeile)</code> als <code>@classmethod</code> und <code>ist_gueltiger_preis(preis)</code> als <code>@staticmethod</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t20_3">
<label for="t20_3" class="task-label">
<strong>TODO 5 (Warenkorb Artikel hinzufügen):</strong> Implementiere <code>artikel_hinzufuegen(*artikel)</code> mit Typprüfung (nur <code>Artikel</code>-Instanzen erlaubt).
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t20_4">
<label for="t20_4" class="task-label">
<strong>TODO 6 (Warenkorb Artikel entfernen):</strong> Implementiere <code>artikel_entfernen(name)</code> (Groß-/Kleinschreibung ignorieren).
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t20_5">
<label for="t20_5" class="task-label">
<strong>TODO 7 (Warenkorb Gesamtsumme):</strong> Implementiere <code>gesamtsumme(brutto=False, mwst_satz=0.19)</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t20_6">
<label for="t20_6" class="task-label">
<strong>TODO 8 (Decorator aufruf_zaehler):</strong> Zähle Aufrufe der dekorierten Funktion und speichere sie in <code>wrapper.aufrufe</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t20_7">
<label for="t20_7" class="task-label">
<strong>TODO 9 (Decorator zeitmessung):</strong> Miss die Laufzeit mit <code>time.perf_counter()</code> und speichere sie in <code>wrapper.letzte_dauer</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t20_8">
<label for="t20_8" class="task-label">
<strong>TODO 10 (Type Hints Formatierer):</strong> Implementiere <code>formatiere_benutzer(daten)</code> mit Fehlerprüfung für fehlende oder leere Benutzernamen.
</label>
</li>
</ul>
<h3>💡 Lösungshinweise (Aufklappen bei Bedarf)</h3>
<details>
<summary>🔍 Tipp zu TODO 8: Aufruf-Zähler als Decorator</summary>
<div class="details-content">
<p>Initialisiere das Attribut am Wrapper vor der Rückgabe:</p>
<div class="code-container">
<pre><span class="kw">def</span> <span class="fn">aufruf_zaehler</span>(<span class="var">func</span>):
<span class="kw">@functools.wraps</span>(<span class="var">func</span>)
<span class="kw">def</span> <span class="fn">wrapper</span>(*<span class="var">args</span>, **<span class="var">kwargs</span>):
<span class="var">wrapper</span>.<span class="attr">aufrufe</span> += <span class="num">1</span>
<span class="kw">return</span> <span class="var">func</span>(*<span class="var">args</span>, **<span class="var">kwargs</span>)
<span class="var">wrapper</span>.<span class="attr">aufrufe</span> = <span class="num">0</span>
<span class="kw">return</span> <span class="var">wrapper</span></pre>
</div>
</div>
</details>
<details>
<summary>🔍 Tipp zu TODO 3: @classmethod als Fabrikmethode</summary>
<div class="details-content">
<p><code>cls</code> ist die Klasse selbst. Teile den String und rufe <code>cls(...)</code> auf:</p>
<div class="code-container">
<pre><span class="kw">@classmethod</span>
<span class="kw">def</span> <span class="fn">aus_csv_zeile</span>(<span class="var">cls</span>, <span class="var">csv_zeile</span>: <span class="var">str</span>) -&gt; <span class="str">"Artikel"</span>:
<span class="var">teile</span> = [t.<span class="fn">strip</span>() <span class="kw">for</span> t <span class="kw">in</span> <span class="var">csv_zeile</span>.<span class="fn">split</span>(<span class="str">";"</span>)]
<span class="kw">return</span> <span class="var">cls</span>(<span class="attr">name</span>=<span class="var">teile</span>[<span class="num">0</span>], <span class="attr">preis</span>=<span class="fn">float</span>(<span class="var">teile</span>[<span class="num">1</span>]), <span class="attr">kategorie</span>=<span class="var">teile</span>[<span class="num">2</span>] <span class="kw">if</span> <span class="fn">len</span>(<span class="var">teile</span>) &gt; <span class="num">2</span> <span class="kw">else</span> <span class="str">"Allgemein"</span>)</pre>
</div>
</div>
</details>
</section>
<!-- 5. Testen & Navigation -->
<section class="card">
<h2>5. 🧪 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 20_dataclasses_und_typehints)</span>
</div>
<pre><code>python3 test_aufgabe.py</code></pre>
</div>
<h3>2. Interaktives 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="../19_generatoren_und_itertools/index.html" class="btn btn-secondary">&larr; Zurück zu Kapitel 19 (Generatoren &amp; itertools)</a>
<a href="../../index.html" class="btn btn-secondary">🏠 Kursübersicht</a>
</div>
</section>
</main>
</div>
</div>
<button type="button" class="sidebar-mobile-toggle"><span>☰ Menü</span></button>
<div class="sidebar-overlay"></div>
<!-- Scripts -->
<script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script>
<script src="../../assets/interpreter.js"></script>
<script src="../../assets/certificate.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/runner.js"></script>
<script src="../../assets/ui_enhancements.js"></script>
<script src="../../assets/sidebar.js"></script>
</body>
</html>

View file

@ -0,0 +1,212 @@
"""
Kapitel 20: Dataclasses, Type Hints & Decorators (Moderne Python-Architektur) 🏷️✨
==================================================================================
Musterlösung für moderne Python-Architektur mit Dataclasses, Typisierung und Decorators.
"""
import time
import functools
from dataclasses import dataclass, field
from typing import Optional, Union, Callable, List, Dict, Any
# ==============================================================================
# TEIL 1: DATACLASSES FÜR TYPSICHERE DATENMODELLE
# ==============================================================================
@dataclass
class Artikel:
"""
Ein Artikel im Online-Shop als moderne Dataclass.
"""
name: str
preis: float
kategorie: str = "Allgemein"
def __post_init__(self) -> None:
if not self.name or not str(self.name).strip():
raise ValueError("Artikelname darf nicht leer sein!")
if float(self.preis) < 0.0:
raise ValueError("Preis darf nicht negativ sein!")
def berechne_bruttopreis(self, mwst_satz: float = 0.19) -> float:
"""
Berechnet den Bruttopreis inklusive Mehrwertsteuer, gerundet auf 2 Nachkommastellen.
"""
if float(mwst_satz) < 0.0:
raise ValueError("Mehrwertsteuersatz darf nicht negativ sein!")
return round(float(self.preis) * (1.0 + float(mwst_satz)), 2)
@classmethod
def aus_csv_zeile(cls, csv_zeile: str) -> "Artikel":
"""
Erstellt ein Artikel-Objekt aus einer CSV-Zeile wie 'Laptop;999.99;Elektronik'.
"""
teile = [t.strip() for t in csv_zeile.split(";")]
if len(teile) < 2:
raise ValueError("CSV-Zeile muss mindestens Name und Preis enthalten!")
name = teile[0]
preis = float(teile[1])
kategorie = teile[2] if len(teile) > 2 and teile[2] else "Allgemein"
return cls(name=name, preis=preis, kategorie=kategorie)
@staticmethod
def ist_gueltiger_preis(preis: Any) -> bool:
"""
Prüft statisch, ob ein Preis gültig (numerisch und >= 0) ist.
"""
if isinstance(preis, (int, float)) and not isinstance(preis, bool):
return preis >= 0.0
return False
@dataclass
class Warenkorb:
"""
Ein digitaler Warenkorb, der Artikel sammelt und verwaltet.
"""
kunde: str
artikel_liste: List[Artikel] = field(default_factory=list)
def artikel_hinzufuegen(self, *artikel: Artikel) -> None:
"""
Fügt beliebig viele Artikel (*args) zum Warenkorb hinzu.
"""
for a in artikel:
if not isinstance(a, Artikel):
raise TypeError(f"Erwartete Artikel-Instanz, erhielt: {type(a).__name__}")
self.artikel_liste.append(a)
def artikel_entfernen(self, artikel_name: str) -> bool:
"""
Entfernt den ersten Artikel mit dem passenden Namen (Groß-/Kleinschreibung ignorieren).
"""
such_name = artikel_name.strip().lower()
for a in self.artikel_liste:
if a.name.strip().lower() == such_name:
self.artikel_liste.remove(a)
return True
return False
def gesamtsumme(self, brutto: bool = False, mwst_satz: float = 0.19) -> float:
"""
Berechnet die Gesamtsumme aller Artikel im Warenkorb.
"""
if not self.artikel_liste:
return 0.0
if brutto:
summe = sum(a.berechne_bruttopreis(mwst_satz) for a in self.artikel_liste)
else:
summe = sum(a.preis for a in self.artikel_liste)
return round(summe, 2)
def __len__(self) -> int:
return len(self.artikel_liste)
# ==============================================================================
# TEIL 2: EIGENE DECORATORS SCHREIBEN
# ==============================================================================
def aufruf_zaehler(func: Callable) -> Callable:
"""
Ein Decorator, der zählt, wie oft eine dekorierte Funktion aufgerufen wurde.
"""
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
wrapper.aufrufe += 1
return func(*args, **kwargs)
wrapper.aufrufe = 0
return wrapper
def zeitmessung(func: Callable) -> Callable:
"""
Ein Decorator, der die Ausführungszeit einer Funktion misst.
"""
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
start = time.perf_counter()
ergebnis = func(*args, **kwargs)
dauer = time.perf_counter() - start
wrapper.letzte_dauer = dauer
return ergebnis
wrapper.letzte_dauer = 0.0
return wrapper
# ==============================================================================
# TEIL 3: FORTGESCHRITTENE TYPE HINTS (UNION, DICT, TYPED DATA)
# ==============================================================================
def formatiere_benutzer(daten: Dict[str, Union[str, int, bool]]) -> str:
"""
Formatiert ein Benutzer-Dictionary mit verschiedenen Datentypen zu einem
lesbaren Status-String.
"""
if "username" not in daten and "name" not in daten:
raise KeyError("Benutzerdaten müssen 'username' oder 'name' enthalten!")
username_val = daten.get("username", daten.get("name"))
if not username_val or not str(username_val).strip():
raise ValueError("Benutzername darf nicht leer sein!")
username = str(username_val).strip()
alter_val = daten.get("alter")
alter_str = str(alter_val) if alter_val is not None else "Unbekannt"
ist_admin = daten.get("ist_admin", False)
rolle = "Administrator" if ist_admin else "Standardbenutzer"
return f"Benutzer: {username} | Alter: {alter_str} | Rolle: {rolle}"
# ==============================================================================
# DEMONSTRATION & MAIN
# ==============================================================================
if __name__ == "__main__":
print("=" * 70)
print("🏷️ KAPITEL 20: MUSTERLÖSUNG – DATACLASSES & DECORATORS")
print("=" * 70)
# 1. Dataclass Artikel testen
laptop = Artikel(name="ThinkPad X1", preis=1499.00, kategorie="Elektronik")
print(f"\n📦 Artikel Dataclass:\n {laptop}")
print(f" Bruttopreis (19%): {laptop.berechne_bruttopreis(0.19):.2f} €")
# 2. CSV Fabrikmethode
maus = Artikel.aus_csv_zeile("Logitech MX Master;99.90;Zubehoer")
print(f" Aus CSV erstellt: {maus}")
# 3. Warenkorb testen
korb = Warenkorb(kunde="Max Mustermann")
korb.artikel_hinzufuegen(laptop, maus, Artikel("Kaffeebecher", 12.50, "Haushalt"))
print(f"\n🛒 Warenkorb für {korb.kunde} ({len(korb)} Artikel):")
print(f" Nettosumme: {korb.gesamtsumme():.2f} €")
print(f" Bruttosumme: {korb.gesamtsumme(brutto=True):.2f} €")
# 4. Aufruf-Zähler Decorator
@aufruf_zaehler
def sage_hallo(name: str) -> str:
return f"Hallo, {name}!"
print("\n🔢 Aufruf-Zähler Decorator Test:")
print(sage_hallo("Alice"))
print(sage_hallo("Bob"))
print(sage_hallo("Charlie"))
print(f" Anzahl Aufrufe: {sage_hallo.aufrufe}")
# 5. Typsicherer Benutzer-Formatierer
user = {"username": "CoderGirl99", "alter": 24, "ist_admin": True}
print(f"\n👤 Benutzerdaten:\n {formatiere_benutzer(user)}")
print("\n" + "=" * 70)
print("✅ Musterlösung erfolgreich ausgeführt!")
print("=" * 70)

View file

@ -0,0 +1,191 @@
import unittest
import sys
import time
from pathlib import Path
from dataclasses import is_dataclass
# Sicherstellen, dass das aktuelle Verzeichnis im Python-Pfad liegt
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel20(unittest.TestCase):
"""
Testsuite für Kapitel 20: Dataclasses, Type Hints & Decorators.
"""
def setUp(self):
self.Artikel = getattr(aufgabe, "Artikel", None)
self.Warenkorb = getattr(aufgabe, "Warenkorb", None)
self.aufruf_zaehler = getattr(aufgabe, "aufruf_zaehler", None)
self.zeitmessung = getattr(aufgabe, "zeitmessung", None)
self.formatiere_benutzer = getattr(aufgabe, "formatiere_benutzer", None)
self.assertIsNotNone(self.Artikel, "Klasse 'Artikel' nicht gefunden!")
self.assertIsNotNone(self.Warenkorb, "Klasse 'Warenkorb' nicht gefunden!")
self.assertIsNotNone(self.aufruf_zaehler, "Decorator 'aufruf_zaehler' nicht gefunden!")
self.assertIsNotNone(self.zeitmessung, "Decorator 'zeitmessung' nicht gefunden!")
self.assertIsNotNone(self.formatiere_benutzer, "Funktion 'formatiere_benutzer' nicht gefunden!")
# ==========================================================================
# 1. Dataclass Artikel
# ==========================================================================
def test_01_artikel_dataclass_basis(self):
"""Prüft, ob Artikel eine echte Dataclass ist und Standardwerte setzt."""
self.assertTrue(is_dataclass(self.Artikel), "Artikel muss mit @dataclass dekoriert sein!")
a = self.Artikel("Buch", 19.99)
self.assertEqual(a.name, "Buch")
self.assertEqual(a.preis, 19.99)
self.assertEqual(a.kategorie, "Allgemein")
# Gleichheit und Repräsentation (Dataclass-Features)
a2 = self.Artikel("Buch", 19.99)
self.assertEqual(a, a2, "Zwei Artikel mit identischen Werten müssen gleich (==) sein!")
self.assertIn("Buch", repr(a))
def test_02_artikel_validierung(self):
"""Prüft die Validierung in __post_init__."""
with self.assertRaises(ValueError, msg="Negativer Preis muss ValueError auslösen!"):
self.Artikel("Test", -5.0)
with self.assertRaises(ValueError, msg="Leerer Name muss ValueError auslösen!"):
self.Artikel("", 10.0)
with self.assertRaises(ValueError, msg="Nur-Leerzeichen-Name muss ValueError auslösen!"):
self.Artikel(" ", 10.0)
def test_03_artikel_berechne_bruttopreis(self):
"""Prüft die Bruttoberechnung mit verschiedenen Steuersätzen."""
buch = self.Artikel("Buch", 10.0)
self.assertEqual(buch.berechne_bruttopreis(0.07), 10.70)
self.assertEqual(buch.berechne_bruttopreis(0.19), 11.90)
self.assertEqual(buch.berechne_bruttopreis(0.0), 10.0)
with self.assertRaises(ValueError, msg="Negativer Steuersatz muss ValueError werfen"):
buch.berechne_bruttopreis(-0.1)
def test_04_artikel_classmethod_und_staticmethod(self):
"""Prüft @classmethod aus_csv_zeile und @staticmethod ist_gueltiger_preis."""
# Classmethod
a = self.Artikel.aus_csv_zeile("Monitor;249.99;Hardware")
self.assertEqual(a.name, "Monitor")
self.assertEqual(a.preis, 249.99)
self.assertEqual(a.kategorie, "Hardware")
# Staticmethod
self.assertTrue(self.Artikel.ist_gueltiger_preis(100))
self.assertTrue(self.Artikel.ist_gueltiger_preis(0.0))
self.assertFalse(self.Artikel.ist_gueltiger_preis(-10))
self.assertFalse(self.Artikel.ist_gueltiger_preis("100"))
# ==========================================================================
# 2. Dataclass Warenkorb
# ==========================================================================
def test_05_warenkorb_basis_und_default_factory(self):
"""Prüft Warenkorb, default_factory und Unabhängigkeit von Instanzen."""
self.assertTrue(is_dataclass(self.Warenkorb), "Warenkorb muss mit @dataclass dekoriert sein!")
k1 = self.Warenkorb(kunde="Alice")
k2 = self.Warenkorb(kunde="Bob")
self.assertEqual(len(k1), 0)
self.assertEqual(k1.gesamtsumme(), 0.0)
# Sicherstellen, dass default_factory=list genutzt wurde (keine geteilte Referenz)
k1.artikel_hinzufuegen(self.Artikel("Tee", 3.50))
self.assertEqual(len(k1), 1)
self.assertEqual(len(k2), 0, "artikel_liste darf nicht zwischen Warenkörben geteilt werden!")
def test_06_warenkorb_artikel_hinzufuegen_und_entfernen(self):
"""Prüft Hinzufügen (*args), Typprüfung und Entfernen von Artikeln."""
korb = self.Warenkorb(kunde="Charlie")
a1 = self.Artikel("Kaffee", 6.00)
a2 = self.Artikel("Keks", 2.00)
korb.artikel_hinzufuegen(a1, a2)
self.assertEqual(len(korb), 2)
self.assertEqual(korb.gesamtsumme(), 8.00)
self.assertEqual(korb.gesamtsumme(brutto=True, mwst_satz=0.10), 8.80)
# Typprüfung
with self.assertRaises(TypeError, msg="Ungültige Objekte müssen TypeError auslösen!"):
korb.artikel_hinzufuegen("Kein Artikel")
# Artikel entfernen
entfernt = korb.artikel_entfernen("kaffee") # Case-insensitive
self.assertTrue(entfernt)
self.assertEqual(len(korb), 1)
self.assertEqual(korb.gesamtsumme(), 2.00)
# Nicht vorhandener Artikel
self.assertFalse(korb.artikel_entfernen("NichtDa"))
# ==========================================================================
# 3. Decorator aufruf_zaehler
# ==========================================================================
def test_07_aufruf_zaehler_decorator(self):
"""Prüft, ob der Decorator Aufrufe zählt und functools.wraps verwendet."""
@self.aufruf_zaehler
def addiere(a: int, b: int) -> int:
"""Addiert zwei Zahlen."""
return a + b
self.assertEqual(addiere.__name__, "addiere", "functools.wraps muss den Funktionsnamen bewahren!")
self.assertEqual(addiere.__doc__, "Addiert zwei Zahlen.", "functools.wraps muss den Docstring bewahren!")
self.assertEqual(addiere.aufrufe, 0)
res1 = addiere(5, 10)
self.assertEqual(res1, 15)
self.assertEqual(addiere.aufrufe, 1)
res2 = addiere(20, 30)
self.assertEqual(res2, 50)
self.assertEqual(addiere.aufrufe, 2)
# ==========================================================================
# 4. Decorator zeitmessung
# ==========================================================================
def test_08_zeitmessung_decorator(self):
"""Prüft, ob zeitmessung die Dauer erfasst und das Ergebnis zurückgibt."""
@self.zeitmessung
def schlafe_kurz():
time.sleep(0.01)
return "Aufgewacht"
ergebnis = schlafe_kurz()
self.assertEqual(ergebnis, "Aufgewacht")
self.assertTrue(hasattr(schlafe_kurz, "letzte_dauer"))
self.assertGreaterEqual(schlafe_kurz.letzte_dauer, 0.005)
# ==========================================================================
# 5. Type Hints formatiere_benutzer
# ==========================================================================
def test_09_formatiere_benutzer(self):
"""Prüft die Formatierung typisierter Benutzer-Dictionaries und Fehler."""
u1 = {"username": "Alice", "alter": 30, "ist_admin": True}
res1 = self.formatiere_benutzer(u1)
self.assertIn("Alice", res1)
self.assertIn("30", res1)
self.assertIn("Administrator", res1)
u2 = {"username": "Bob", "ist_admin": False}
res2 = self.formatiere_benutzer(u2)
self.assertIn("Bob", res2)
self.assertIn("Unbekannt", res2)
self.assertIn("Standardbenutzer", res2)
# Fehler bei fehlendem Username
with self.assertRaises(KeyError):
self.formatiere_benutzer({"alter": 20})
with self.assertRaises(ValueError):
self.formatiere_benutzer({"username": " "})
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,48 @@
# Kapitel 21: Relationale Datenbanken & SQLite (sqlite3) 🗄️⚡
In diesem Kapitel lernst du, wie du strukturierte relationale Datenbanken mit Pythons eingebautem Modul `sqlite3` erstellst, verwaltest und sicher abfragst.
---
## 🎯 Was du lernst
1. **Relationale Datenbanken vs. Einfache Textdateien:** Warum relationale Datenbanken (Tabellen, Primärschlüssel, Relationen) für große Datenmengen und gleichzeitigen Zugriff unverzichtbar sind.
2. **Die Aktenschrank-Analogie:**
- **Datenbankdatei:** Der Aktenschrank.
- **Tabelle:** Eine Schublade mit Registerkarten.
- **Spalte (Column):** Ein vorgegebenes Feld auf der Registerkarte (z.B. Name, E-Mail, Guthaben).
- **Zeile (Row/Record):** Eine einzelne ausgefüllte Registerkarte (ein Kunde).
3. **Verbindung & Cursor:**
```python
import sqlite3
conn = sqlite3.connect("kunden.db") # oder ":memory:" für flüchtige RAM-Datenbank
conn.row_factory = sqlite3.Row # Zugriff per Spaltennamen wie im Dict!
cursor = conn.cursor()
```
4. **CRUD-Operationen in SQL:**
- **C**reate: `CREATE TABLE IF NOT EXISTS ...` und `INSERT INTO ...`
- **R**ead: `SELECT ... FROM ... WHERE ...`
- **U**pdate: `UPDATE ... SET ... WHERE ...`
- **D**elete: `DELETE FROM ... WHERE ...`
5. **Transaktionen & Persistenz:** Warum Änderungen erst nach `conn.commit()` dauerhaft auf der Festplatte gespeichert werden.
6. **Sicherheit & SQL-Injection-Schutz:** Niemals Strings formatieren (`f"SELECT ... {user_input}"`), sondern immer Platzhalter (`?`) verwenden!
---
## 📁 Die Dateien in diesem Ordner
- **`index.html`**: Die interaktive Lernseite mit Aktenschrank-Analogie, SQL-Cheat-Sheet, Injection-Demo und Subgoals.
- **`aufgabe.py`**: Dein Arbeitsblatt mit der Klasse `KundenDatenbank`.
- **`test_aufgabe.py`**: Automatische Unittest-Suite (`python3 test_aufgabe.py`).
- **`musterloesung.py`**: Vollständig ausprogrammierte Beispiellösung.
---
## 🚀 Schnellstart
```bash
# 1. Bearbeite aufgabe.py
# 2. Teste deine Lösung:
python3 test_aufgabe.py
```

View file

@ -0,0 +1,169 @@
"""
Kapitel 21: Relationale Datenbanken & SQLite (sqlite3) 🗄️⚡
=========================================================
Lehrpfad 4: Professional Data Engineering
Thema:
Relationales Datenmanagement mit SQLite und Pythons eingebautem Modul `sqlite3`.
Lernziele:
1. Verbindungsaufbau mit `sqlite3.connect()` und Arbeiten mit dem Cursor `conn.cursor()`.
2. Tabellen definieren mit `CREATE TABLE IF NOT EXISTS`.
3. Datensätze einfügen mit `INSERT INTO` und `conn.commit()`.
4. Daten gezielt abfragen mit `SELECT ... WHERE` und `fetchall()` / `fetchone()`.
5. Datensätze aktualisieren mit `UPDATE ... SET` und löschen mit `DELETE FROM`.
6. Schutz vor SQL-Injections durch parametrisierte Abfragen (`?`).
7. Verwendung von `row_factory = sqlite3.Row` für benannte Spaltenzugriffe.
"""
import sqlite3
from typing import List, Optional, Dict, Any
class KundenDatenbank:
"""
Verwaltet Kundenkonten und Guthaben in einer relationalen SQLite-Datenbank.
"""
def __init__(self, db_pfad: str = ":memory:"):
"""
Initialisiert die Datenbankverbindung und erstellt die Tabelle 'kunden',
falls sie noch nicht existiert.
Tabelle 'kunden':
- id: INTEGER PRIMARY KEY AUTOINCREMENT
- name: TEXT NOT NULL
- email: TEXT NOT NULL UNIQUE
- guthaben: REAL DEFAULT 0.0
Tipps:
- self.db_pfad = db_pfad
- self.conn = sqlite3.connect(db_pfad)
- self.conn.row_factory = sqlite3.Row # Ermöglicht dict-artigen Zugriff
- self.cursor = self.conn.cursor()
- Führe den CREATE TABLE Befehl mit self.cursor.execute(...) aus
- Führe self.conn.commit() aus
"""
# TODO: Implementieren
pass
def kunde_hinzufuegen(self, name: str, email: str, startguthaben: float = 0.0) -> int:
"""
Fügt einen neuen Kunden in die Datenbank ein.
WICHTIG:
- Verwende IMMER parametrisierte SQL-Queries mit '?' gegen SQL-Injections!
- SQL: "INSERT INTO kunden (name, email, guthaben) VALUES (?, ?, ?)"
- Führe nach dem INSERT self.conn.commit() auf.
Rückgabewert:
- Die automatisch vergebene ID des neuen Kunden (int) via self.cursor.lastrowid
"""
# TODO: Implementieren
pass
def kunde_suchen_nach_id(self, kunden_id: int) -> Optional[Dict[str, Any]]:
"""
Sucht einen Kunden anhand seiner ID.
SQL:
"SELECT id, name, email, guthaben FROM kunden WHERE id = ?"
Rückgabewert:
- Ein Dictionary mit den Spalten als Keys:
{"id": row["id"], "name": row["name"], "email": row["email"], "guthaben": row["guthaben"]}
- None, falls kein Kunde mit dieser ID gefunden wurde.
"""
# TODO: Implementieren
pass
def guthaben_aufladen(self, kunden_id: int, betrag: float) -> bool:
"""
Lädt das Guthaben eines bestehenden Kunden um den angegebenen Betrag auf.
Bedingungen:
- betrag muss größer als 0 sein (andernfalls direkt False zurückgeben).
- SQL: "UPDATE kunden SET guthaben = guthaben + ? WHERE id = ?"
- Führe self.conn.commit() aus.
Rückgabewert:
- True, wenn der Datensatz aktualisiert wurde (self.cursor.rowcount > 0).
- False, wenn kein Kunde mit dieser ID existiert oder betrag <= 0 ist.
"""
# TODO: Implementieren
pass
def kunden_mit_mindestguthaben(self, min_guthaben: float) -> List[Dict[str, Any]]:
"""
Gibt alle Kunden zurück, deren Guthaben >= min_guthaben ist.
Sortiert nach Guthaben absteigend (höchstes Guthaben zuerst).
SQL:
"SELECT id, name, email, guthaben FROM kunden WHERE guthaben >= ? ORDER BY guthaben DESC"
Rückgabewert:
- Liste von Dictionaries der passenden Kunden.
[{"id": ..., "name": ..., "email": ..., "guthaben": ...}, ...]
"""
# TODO: Implementieren
pass
def kunde_loeschen(self, kunden_id: int) -> bool:
"""
Löscht einen Kunden anhand seiner ID aus der Datenbank.
SQL:
"DELETE FROM kunden WHERE id = ?"
Rückgabewert:
- True, wenn ein Kunde gelöscht wurde (self.cursor.rowcount > 0).
- False, wenn kein Kunde mit dieser ID existierte.
"""
# TODO: Implementieren
pass
def alle_kunden(self) -> List[Dict[str, Any]]:
"""
Gibt eine Liste aller Kunden zurück.
"""
# TODO: Implementieren
pass
def schliessen(self) -> None:
"""Schließt die Datenbankverbindung."""
if hasattr(self, "conn") and self.conn:
self.conn.close()
# ==============================================================================
# Interaktiver Test im Terminal:
# (python3 aufgabe.py)
# ==============================================================================
if __name__ == "__main__":
print("🗄️ Initialisiere KundenDatenbank im Arbeitsspeicher (:memory:)...")
db = KundenDatenbank(":memory:")
# 1. Kunden anlegen
id1 = db.kunde_hinzufuegen("Alice Schmidt", "alice@example.com", 150.0)
id2 = db.kunde_hinzufuegen("Bob Müller", "bob@example.com", 50.0)
id3 = db.kunde_hinzufuegen("Charlie Braun", "charlie@example.com", 320.0)
print(f"Kunden erstellt: IDs = {id1}, {id2}, {id3}")
# 2. Kunde suchen
kunde = db.kunde_suchen_nach_id(id1)
print(f"Gefundener Kunde: {kunde}")
# 3. Guthaben aufladen
erfolg = db.guthaben_aufladen(id2, 100.0)
print(f"Guthaben von Bob aufgeladen: {erfolg} -> Neuer Stand: {db.kunde_suchen_nach_id(id2)}")
# 4. Mindestguthaben filtern
reiche_kunden = db.kunden_mit_mindestguthaben(140.0)
print(f"Kunden mit mind. 140€ Guthaben: {reiche_kunden}")
# 5. Kunde löschen
db.kunde_loeschen(id3)
print(f"Nach Löschen von Charlie: {db.alle_kunden()}")
db.schliessen()
print("Fertig!")

View file

@ -0,0 +1,502 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapitel 21: Relationale Datenbanken &amp; SQLite</title>
<link rel="stylesheet" href="../../assets/style.css">
</head>
<body>
<div class="app-layout">
<aside class="app-sidebar">
<div class="sidebar-header">
<a href="../../index.html" class="sidebar-brand-link">
<div class="sidebar-logo">🐍</div>
<div>
<div class="sidebar-title">Python Portal</div>
<div class="sidebar-subtitle">Lernplattform</div>
</div>
</a>
</div>
<div class="sidebar-quick-nav">
<a href="../../index.html" class="sidebar-link">🏠 Startseite</a>
<a href="../../index.html#tutorial" class="sidebar-link">📖 Tutorial &amp; Workflow</a>
<a href="../../index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a>
</div>
<div class="sidebar-nav">
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🌱 1. Grundlagen</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">G01–G10</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_1_grundlagen/01_erste_schritte_taschenrechner/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧮</span>
<span class="sidebar-link-text">G01. Taschenrechner</span>
</a>
<a href="../../lehrpfad_1_grundlagen/02_variablen_und_datentypen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📦</span>
<span class="sidebar-link-text">G02. Variablen &amp; Typen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/03_ein_und_ausgabe/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💬</span>
<span class="sidebar-link-text">G03. Ein- &amp; Ausgabe</span>
</a>
<a href="../../lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚦</span>
<span class="sidebar-link-text">G04. Bedingungen (if)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎡</span>
<span class="sidebar-link-text">G05. Schleifen (for/while)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/06_funktionen_und_module/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🪄</span>
<span class="sidebar-link-text">G06. Funktionen &amp; Module</span>
</a>
<a href="../../lehrpfad_1_grundlagen/07_listen_und_sequenzen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📋</span>
<span class="sidebar-link-text">G07. Listen &amp; Sequenzen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/08_textverarbeitung_und_strings/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔤</span>
<span class="sidebar-link-text">G08. String-Methoden</span>
</a>
<a href="../../lehrpfad_1_grundlagen/09_dictionaries_und_sets/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗃️</span>
<span class="sidebar-link-text">G09. Dicts &amp; Sets</span>
</a>
<a href="../../lehrpfad_1_grundlagen/10_comprehensions_datum_algorithmen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚀</span>
<span class="sidebar-link-text">G10. Comprehensions</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🏗️ 2. OOP Einstieg</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">00–06</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">00. Fehlersuche &amp; Debug</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/01_einstieg_klassen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏗️</span>
<span class="sidebar-link-text">01. Einstieg in Klassen</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/02_init_und_self/index.html" class="sidebar-link">
<span class="sidebar-link-icon">⚙️</span>
<span class="sidebar-link-text">02. Konstruktor &amp; self</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/03_methoden_und_verhalten/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏎️</span>
<span class="sidebar-link-text">03. Methoden &amp; Verhalten</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04_str_und_darstellung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧾</span>
<span class="sidebar-link-text">04. __str__ Darstellung</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04b_umstieg_vscode/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💻</span>
<span class="sidebar-link-text">04b. VS Code Setup</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04c_git_und_versionskontrolle/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌿</span>
<span class="sidebar-link-text">04c. Git-Versionskontrolle</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/05_objekte_kombinieren/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎵</span>
<span class="sidebar-link-text">05. Objekte kombinieren</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🥚</span>
<span class="sidebar-link-text">06. Projekt: Tamagotchi</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🚀 3. Fortgeschritten</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">07–16</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_3_fortgeschrittenes_oop/07_referenzen_und_speicher/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧠</span>
<span class="sidebar-link-text">07. Referenzen &amp; Speicher</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/08_operator_overloading_dunder/index.html" class="sidebar-link">
<span class="sidebar-link-icon">➕</span>
<span class="sidebar-link-text">08. Operator Overloading</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧪</span>
<span class="sidebar-link-text">09. Unit Tests &amp; TDD</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧬</span>
<span class="sidebar-link-text">10. Vererbung &amp; super()</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/11_polymorphie_und_interfaces/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎭</span>
<span class="sidebar-link-text">11. Polymorphie</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/12_exceptions_und_fehlerbehandlung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🛡️</span>
<span class="sidebar-link-text">12. Exceptions &amp; Fehler</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/13_persistenz_json_und_csv/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💾</span>
<span class="sidebar-link-text">13. Persistenz (JSON/CSV)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/14_gui_mit_tkinter/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🖥️</span>
<span class="sidebar-link-text">14. Desktop-GUIs (Tkinter)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/15_parameter_und_container/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎒</span>
<span class="sidebar-link-text">15. Container &amp; *args</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏆</span>
<span class="sidebar-link-text">16. Master-Abschlussprojekt</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>📊 4. Data Engineering</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">21–22</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_4_professional_data_engineering/21_datenbanken_und_sqlite/index.html" class="sidebar-link active">
<span class="sidebar-link-icon">🗄️</span>
<span class="sidebar-link-text">21. Datenbanken &amp; SQLite</span>
</a>
<a href="../../lehrpfad_4_professional_data_engineering/22_web_apis_und_json_feeds/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌐</span>
<span class="sidebar-link-text">22. Web-APIs &amp; JSON</span>
</a>
</div>
</div>
</div>
<div class="sidebar-footer">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="font-size: 0.85rem; font-weight: 700; color: white;" id="sidebar-level-text">⭐ Level 1</div>
<div style="font-size: 0.82rem; font-weight: 700; color: #38bdf8;" id="sidebar-xp-text">0 XP</div>
</div>
</div>
</aside>
<div class="app-main">
<header>
<div class="header-container">
<nav class="nav-links">
<a href="../../index.html">🏠 Zur Kursübersicht</a>
<span>&rsaquo;</span>
<span>Lehrpfad 4</span>
<span>&rsaquo;</span>
<span style="color: white; font-weight: 600;">Kapitel 21</span>
</nav>
<div class="header-badge">Kapitel 21 &bull; Data Engineering &bull; SQL &amp; Persistenz</div>
<h1>Relationale Datenbanken &amp; SQLite 🗄️⚡</h1>
<p>Lerne die Grundlagen relationaler Datenbanken mit Pythons integriertem <code>sqlite3</code>-Modul, meistere CRUD-Operationen und schütze deine Daten vor SQL-Injections.</p>
</div>
</header>
<main class="container">
<!-- 📂 Schnellzugriff & Download aller Dateien dieser Einheit -->
<div style="background: #ffffff; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 14px 20px; margin-bottom: 25px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; box-shadow: var(--shadow-sm);">
<div style="font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 8px;">
<span>📂 Dateien dieser Einheit:</span>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="aufgabe.py" class="btn" style="padding: 6px 14px; font-size: 0.88rem; background: var(--primary);" download>📄 aufgabe.py (Startercode)</a>
<a href="test_aufgabe.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>🧪 test_aufgabe.py (Tests)</a>
<a href="musterloesung.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>💡 musterloesung.py (Lösung)</a>
<a href="README.md" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;">📖 README.md</a>
</div>
</div>
<!-- 1. Analogie & Konzept -->
<section class="card">
<h2>1. Was ist eine relationale Datenbank? (Die Aktenschrank-Analogie)</h2>
<p>
In vorherigen Kapiteln haben wir Daten in einfachen JSON- oder CSV-Dateien gespeichert. Bei größeren Anwendungen stoßen Textdateien jedoch schnell an ihre Grenzen:
Man kann nicht blitzschnell nach einzelnen Datensätzen suchen, ohne die gesamte Datei einzulesen, und gleichzeitige Schreibzugriffe führen zu Datenverlust.
Hier kommen <strong>relationale Datenbanksysteme (RDBMS)</strong> ins Spiel!
</p>
<div class="box box-analogie">
<div class="box-title">🗄️ Die Aktenschrank-Analogie:</div>
Stell dir eine relationale Datenbank wie einen soliden Büroschrank vor:
<ul style="margin-top: 8px; margin-left: 20px;">
<li><strong>Die Datenbankdatei (<code>.db</code>):</strong> Der gesamte Aktenschrank im Büro.</li>
<li><strong>Die Tabelle (<code>Table</code>):</strong> Eine einzelne ausziehbare Schublade (z.B. Schublade „Kunden“).</li>
<li><strong>Die Spalte (<code>Column / Field</code>):</strong> Ein vorgedrucktes Formularfeld auf den Karteikarten (z.B. „Name“, „E-Mail“, „Guthaben“).</li>
<li><strong>Die Zeile (<code>Row / Record</code>):</strong> Eine einzelne, ausgefüllte Karteikarte für genau einen Kunden.</li>
<li><strong>Der Primärschlüssel (<code>PRIMARY KEY</code>):</strong> Die eindeutige Kundennummer oben rechts auf jeder Karteikarte.</li>
</ul>
</div>
<h3>Der Lebenszyklus einer SQLite-Verbindung in Python</h3>
<p>
Python bringt mit <code>sqlite3</code> eine vollständige, vollwertige SQL-Datenbank direkt in der Standardbibliothek mit – ohne Server-Installation!
</p>
<div class="code-container">
<div class="code-header">
<span>Grundgerüst mit sqlite3</span>
</div>
<pre><span class="kw">import</span> <span class="var">sqlite3</span>
<span class="cm"># 1. Verbindung herstellen (erstellt die Datei automatisch, falls nicht vorhanden)</span>
<span class="var">conn</span> = <span class="var">sqlite3</span>.<span class="fn">connect</span>(<span class="str">"kunden.db"</span>)
<span class="cm"># 2. row_factory aktivieren: Erlaubt Dict-artigen Spaltenzugriff wie row["name"]</span>
<span class="var">conn</span>.<span class="attr">row_factory</span> = <span class="var">sqlite3</span>.<span class="var">Row</span>
<span class="cm"># 3. Cursor erzeugen (der 'Stift', mit dem wir Befehle an die DB schicken)</span>
<span class="var">cursor</span> = <span class="var">conn</span>.<span class="fn">cursor</span>()
<span class="cm"># 4. SQL-Befehl ausführen</span>
<span class="var">cursor</span>.<span class="fn">execute</span>(<span class="str">"""
CREATE TABLE IF NOT EXISTS kunden (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
guthaben REAL DEFAULT 0.0
)
"""</span>)
<span class="cm"># 5. Transaktion dauerhaft speichern (Commit)</span>
<span class="var">conn</span>.<span class="fn">commit</span>()
<span class="cm"># 6. Verbindung sauber schließen</span>
<span class="var">conn</span>.<span class="fn">close</span>()</pre>
</div>
</section>
<!-- 2. CRUD SQL Befehle -->
<section class="card">
<h2>2. Die 4 Säulen von SQL: CRUD im Überblick</h2>
<p>In relationalen Datenbanken dreht sich alles um die vier elementaren <strong>CRUD</strong>-Operationen:</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);">Operation</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">SQL-Befehl</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Python-Beispiel mit Platzhaltern (<code>?</code>)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Create</strong> (Erstellen)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>INSERT INTO ...</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>cursor.execute("INSERT INTO kunden (name, email, guthaben) VALUES (?, ?, ?)", (name, email, 50.0))</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Read</strong> (Lesen / Suchen)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>SELECT ... WHERE</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>cursor.execute("SELECT * FROM kunden WHERE id = ?", (kunden_id,))</code><br><code>row = cursor.fetchone()</code></td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Update</strong> (Aktualisieren)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>UPDATE ... SET</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>cursor.execute("UPDATE kunden SET guthaben = guthaben + ? WHERE id = ?", (betrag, kunden_id))</code></td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><strong>Delete</strong> (Löschen)</td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>DELETE FROM ...</code></td>
<td style="padding: 10px; border: 1px solid var(--border-color);"><code>cursor.execute("DELETE FROM kunden WHERE id = ?", (kunden_id,))</code></td>
</tr>
</tbody>
</table>
<div class="box" style="background: #eff6ff; border-left: 4px solid #3b82f6;">
<div class="box-title" style="color: #1d4ed8;">💡 Cursor-Methoden im Detail:</div>
<ul style="margin-left: 20px; margin-top: 6px;">
<li><code>cursor.fetchone()</code>: Liefert die nächste einzelne Ergebniszeile (oder <code>None</code>).</li>
<li><code>cursor.fetchall()</code>: Liefert eine Liste aller passenden Ergebniszeilen (oder <code>[]</code>).</li>
<li><code>cursor.lastrowid</code>: Die automatisch generierte ID des zuletzt eingefügten Datensatzes.</li>
<li><code>cursor.rowcount</code>: Die Anzahl der Zeilen, die durch das letzte <code>UPDATE</code> oder <code>DELETE</code> verändert wurden.</li>
</ul>
</div>
</section>
<!-- 3. SQL Injection Sicherheit -->
<section class="card">
<h2>3. 🚨 Sicherheit: Schutz vor SQL-Injections mit <code>?</code>-Parametern</h2>
<p>
Eine der häufigsten und gefährlichsten Sicherheitslücken in der Software-Entwicklung ist die <strong>SQL-Injection</strong>.
Sie entsteht, wenn Benutzereingaben unbedacht per String-Formatierung (z.B. f-Strings) direkt in den SQL-Befehl eingebettet werden!
</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 15px 0;">
<div style="background: #fff1f2; border: 1px solid #fecdd3; border-radius: var(--radius-md); padding: 14px;">
<h4 style="color: #e11d48; margin-bottom: 8px;">❌ GEFÄHRLICH: String-Formatierung</h4>
<pre style="background: #0b0f19; color: #f87171; padding: 10px; border-radius: 6px; font-size: 0.82rem; overflow-x: auto;"><code># Angreifer gibt als Name ein:
# Robert'); DROP TABLE kunden;--
query = f"INSERT INTO kunden (name) VALUES ('{user_name}')"
cursor.execute(query)
# BUMM! Die Tabelle kunden wird gelöscht!</code></pre>
</div>
<div style="background: #ecfdf5; border: 1px solid #a7f3d0; border-radius: var(--radius-md); padding: 14px;">
<h4 style="color: #059669; margin-bottom: 8px;">✅ SICHER: Parametrisierte Abfrage</h4>
<pre style="background: #0b0f19; color: #34d399; padding: 10px; border-radius: 6px; font-size: 0.82rem; overflow-x: auto;"><code># Das sqlite3-Modul maskiert alle Zeichen:
query = "INSERT INTO kunden (name) VALUES (?)"
cursor.execute(query, (user_name,))
# Der Name wird harmlos als Text gespeichert!</code></pre>
</div>
</div>
</section>
<!-- 4. In-Memory SQLite -->
<section class="card">
<h2>4. In-Memory Datenbanken mit <code>:memory:</code></h2>
<p>
Für automatisierte Unit Tests oder temporäre Berechnungen bietet SQLite ein geniales Feature: die <strong>In-Memory-Datenbank</strong>.
</p>
<div class="code-container">
<pre><span class="var">conn</span> = <span class="var">sqlite3</span>.<span class="fn">connect</span>(<span class="str">":memory:"</span>)</pre>
</div>
<p>
Die Datenbank existiert ausschließlich im flüchtigen RAM. Sobald <code>conn.close()</code> aufgerufen wird oder der Test endet, ist alles rückstandslos bereinigt –
ideal für blitzschnelle, voneinander isolierte Tests ohne lästige temporäre Testdateien auf der Festplatte!
</p>
</section>
<!-- 5. Aufgabenstellung -->
<section class="card">
<h2>5. 🎯 Deine Aufgaben in <code><a href="aufgabe.py" style="color: inherit; text-decoration: underline;">aufgabe.py</a></code></h2>
<p>Öffne die Datei <code><a href="aufgabe.py" style="color: inherit; text-decoration: underline;">aufgabe.py</a></code> und implementiere die Klasse <code>KundenDatenbank</code>:</p>
<ul class="task-list">
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t21_1">
<label for="t21_1" class="task-label">
<strong>TODO 1 (Konstruktor):</strong> Verbindung zu <code>db_pfad</code> aufbauen, <code>row_factory = sqlite3.Row</code> setzen, Cursor erstellen und Tabelle <code>kunden</code> mit <code>id</code>, <code>name</code>, <code>email</code> und <code>guthaben</code> anlegen.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t21_2">
<label for="t21_2" class="task-label">
<strong>TODO 2 (kunde_hinzufuegen):</strong> Per <code>INSERT INTO kunden ... VALUES (?, ?, ?)</code> Datensatz einfügen, <code>commit()</code> aufrufen und <code>cursor.lastrowid</code> zurückgeben.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t21_3">
<label for="t21_3" class="task-label">
<strong>TODO 3 (kunde_suchen_nach_id):</strong> Per <code>SELECT</code> nach ID suchen, <code>cursor.fetchone()</code> prüfen und ein Dictionary oder <code>None</code> zurückgeben.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t21_4">
<label for="t21_4" class="task-label">
<strong>TODO 4 (guthaben_aufladen):</strong> Prüfen, ob Betrag > 0 ist, mit <code>UPDATE kunden SET guthaben = guthaben + ? WHERE id = ?</code> aktualisieren und <code>cursor.rowcount > 0</code> zurückgeben.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t21_5">
<label for="t21_5" class="task-label">
<strong>TODO 5 (kunden_mit_mindestguthaben):</strong> Mit <code>SELECT ... WHERE guthaben >= ? ORDER BY guthaben DESC</code> abfragen und Liste von Dicts zurückgeben.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t21_6">
<label for="t21_6" class="task-label">
<strong>TODO 6 (kunde_loeschen):</strong> Mit <code>DELETE FROM kunden WHERE id = ?</code> Kunde löschen und <code>cursor.rowcount > 0</code> zurückgeben.
</label>
</li>
</ul>
<h3>💡 Lösungshinweise (Aufklappen bei Bedarf)</h3>
<details>
<summary>🔍 Tipp zur Tabellenerstellung</summary>
<div class="details-content">
<div class="code-container">
<pre><span class="var">self</span>.<span class="attr">cursor</span>.<span class="fn">execute</span>(<span class="str">"""
CREATE TABLE IF NOT EXISTS kunden (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
guthaben REAL DEFAULT 0.0
)
"""</span>)
<span class="var">self</span>.<span class="attr">conn</span>.<span class="fn">commit</span>()</pre>
</div>
</div>
</details>
<details>
<summary>🔍 Tipp zu row_factory &amp; Dict-Rückgabe</summary>
<div class="details-content">
<div class="code-container">
<pre><span class="var">self</span>.<span class="attr">cursor</span>.<span class="fn">execute</span>(<span class="str">"SELECT id, name, email, guthaben FROM kunden WHERE id = ?"</span>, (<span class="var">kunden_id</span>,))
<span class="var">row</span> = <span class="var">self</span>.<span class="attr">cursor</span>.<span class="fn">fetchone</span>()
<span class="kw">if</span> <span class="var">row</span> <span class="kw">is None</span>:
<span class="kw">return None</span>
<span class="kw">return</span> {
<span class="str">"id"</span>: <span class="var">row</span>[<span class="str">"id"</span>],
<span class="str">"name"</span>: <span class="var">row</span>[<span class="str">"name"</span>],
<span class="str">"email"</span>: <span class="var">row</span>[<span class="str">"email"</span>],
<span class="str">"guthaben"</span>: <span class="fn">float</span>(<span class="var">row</span>[<span class="str">"guthaben"</span>])
}</pre>
</div>
</div>
</details>
</section>
<!-- 6. Testen & Navigation -->
<section class="card">
<h2>6. 🧪 Lösung überprüfen</h2>
<p>Führe im Terminal im Ordner <code>21_datenbanken_und_sqlite</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="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="btn btn-secondary">&larr; Zurück zu Kapitel 16</a>
<a href="../22_web_apis_und_json_feeds/index.html" class="btn">Weiter zu Kapitel 22 &rarr;</a>
</div>
</section>
</main>
</div>
</div>
<button type="button" class="sidebar-mobile-toggle"><span>☰ Menü</span></button>
<div class="sidebar-overlay"></div>
<!-- Scripts -->
<script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script>
<script src="../../assets/interpreter.js"></script>
<script src="../../assets/certificate.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/runner.js"></script>
<script src="../../assets/ui_enhancements.js"></script>
<script src="../../assets/sidebar.js"></script>
</body>
</html>

View file

@ -0,0 +1,140 @@
"""
Kapitel 21: Relationale Datenbanken & SQLite (sqlite3) 🗄️⚡
=========================================================
Lehrpfad 4: Professional Data Engineering
Musterlösung für die KundenDatenbank mit SQLite3.
"""
import sqlite3
from typing import List, Optional, Dict, Any
class KundenDatenbank:
"""
Vollständig implementierte KundenDatenbank mit sqlite3.
"""
def __init__(self, db_pfad: str = ":memory:"):
"""
Initialisiert die Datenbankverbindung und erstellt die Tabelle 'kunden'.
"""
self.db_pfad = db_pfad
self.conn = sqlite3.connect(db_pfad)
self.conn.row_factory = sqlite3.Row
self.cursor = self.conn.cursor()
# Tabelle anlegen
self.cursor.execute(
"""
CREATE TABLE IF NOT EXISTS kunden (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
guthaben REAL DEFAULT 0.0
)
"""
)
self.conn.commit()
def kunde_hinzufuegen(self, name: str, email: str, startguthaben: float = 0.0) -> int:
"""
Fügt einen neuen Kunden per parametrisiertem INSERT ein.
"""
self.cursor.execute(
"INSERT INTO kunden (name, email, guthaben) VALUES (?, ?, ?)",
(name, email, float(startguthaben))
)
self.conn.commit()
return self.cursor.lastrowid
def kunde_suchen_nach_id(self, kunden_id: int) -> Optional[Dict[str, Any]]:
"""
Sucht einen Kunden anhand der ID und liefert ein Dictionary zurück.
"""
self.cursor.execute(
"SELECT id, name, email, guthaben FROM kunden WHERE id = ?",
(kunden_id,)
)
row = self.cursor.fetchone()
if row is None:
return None
return {
"id": row["id"],
"name": row["name"],
"email": row["email"],
"guthaben": float(row["guthaben"]),
}
def guthaben_aufladen(self, kunden_id: int, betrag: float) -> bool:
"""
Erhöht das Guthaben eines Kunden um einen positiven Betrag.
"""
if betrag <= 0:
return False
self.cursor.execute(
"UPDATE kunden SET guthaben = guthaben + ? WHERE id = ?",
(float(betrag), kunden_id)
)
self.conn.commit()
return self.cursor.rowcount > 0
def kunden_mit_mindestguthaben(self, min_guthaben: float) -> List[Dict[str, Any]]:
"""
Liefert alle Kunden mit mindestens min_guthaben, sortiert absteigend.
"""
self.cursor.execute(
"SELECT id, name, email, guthaben FROM kunden WHERE guthaben >= ? ORDER BY guthaben DESC",
(float(min_guthaben),)
)
rows = self.cursor.fetchall()
return [
{
"id": r["id"],
"name": r["name"],
"email": r["email"],
"guthaben": float(r["guthaben"]),
}
for r in rows
]
def kunde_loeschen(self, kunden_id: int) -> bool:
"""
Löscht einen Kunden anhand der ID.
"""
self.cursor.execute(
"DELETE FROM kunden WHERE id = ?",
(kunden_id,)
)
self.conn.commit()
return self.cursor.rowcount > 0
def alle_kunden(self) -> List[Dict[str, Any]]:
"""
Gibt eine Liste aller Kunden zurück.
"""
self.cursor.execute("SELECT id, name, email, guthaben FROM kunden ORDER BY id ASC")
rows = self.cursor.fetchall()
return [
{
"id": r["id"],
"name": r["name"],
"email": r["email"],
"guthaben": float(r["guthaben"]),
}
for r in rows
]
def schliessen(self) -> None:
"""Schließt die Verbindung."""
if hasattr(self, "conn") and self.conn:
self.conn.close()
if __name__ == "__main__":
db = KundenDatenbank(":memory:")
i1 = db.kunde_hinzufuegen("Max Mustermann", "max@test.de", 100.0)
print("Kunde hinzugefügt mit ID:", i1)
print("Kunde gesucht:", db.kunde_suchen_nach_id(i1))
db.schliessen()

View file

@ -0,0 +1,156 @@
import unittest
import sys
import sqlite3
from pathlib import Path
# Sicherstellen, dass das aktuelle Verzeichnis im Python-Pfad liegt
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel21(unittest.TestCase):
"""
Umfassende Testsuite für Modul 21 (SQLite Datenbanken & Persistenz).
Nutzt isolierte In-Memory-Datenbanken (:memory:) für maximale Geschwindigkeit.
"""
def setUp(self):
self.KundenDatenbank = getattr(aufgabe, "KundenDatenbank", None)
self.assertIsNotNone(self.KundenDatenbank, "Klasse 'KundenDatenbank' nicht in aufgabe.py gefunden!")
# Jede Testmethode erhält eine frische, isolierte In-Memory-Datenbank
self.db = self.KundenDatenbank(":memory:")
def tearDown(self):
if hasattr(self, "db") and self.db:
self.db.schliessen()
def test_01_tabelle_und_initialisierung(self):
"""Prüft, ob die Tabelle 'kunden' mit den korrekten Spalten angelegt wurde."""
cursor = self.db.conn.cursor()
cursor.execute("PRAGMA table_info(kunden)")
spalten = {row[1]: row[2].upper() for row in cursor.fetchall()}
self.assertIn("id", spalten, "Spalte 'id' fehlt in Tabelle 'kunden'!")
self.assertIn("name", spalten, "Spalte 'name' fehlt in Tabelle 'kunden'!")
self.assertIn("email", spalten, "Spalte 'email' fehlt in Tabelle 'kunden'!")
self.assertIn("guthaben", spalten, "Spalte 'guthaben' fehlt in Tabelle 'kunden'!")
def test_02_kunde_hinzufuegen(self):
"""Prüft das Einfügen von Kunden und die Rückgabe der neuen ID."""
id1 = self.db.kunde_hinzufuegen("Alice Schmidt", "alice@example.com", 150.0)
self.assertIsInstance(id1, int, "kunde_hinzufuegen muss die int-ID zurückgeben!")
self.assertEqual(id1, 1, "Der erste Kunde sollte ID 1 erhalten!")
id2 = self.db.kunde_hinzufuegen("Bob Müller", "bob@example.com", 75.50)
self.assertEqual(id2, 2, "Der zweite Kunde sollte ID 2 erhalten!")
# Prüfen, ob Datensätze direkt in der DB liegen
c = self.db.conn.cursor()
c.execute("SELECT COUNT(*) FROM kunden")
anzahl = c.fetchone()[0]
self.assertEqual(anzahl, 2, "Es sollten genau 2 Kunden in der Datenbank vorhanden sein!")
def test_03_kunde_suchen_nach_id(self):
"""Prüft die Suche nach existierenden und nicht-existierenden Kunden."""
id_neu = self.db.kunde_hinzufuegen("Clara Oswald", "clara@tardis.org", 250.0)
# Existierender Kunde
kunde = self.db.kunde_suchen_nach_id(id_neu)
self.assertIsInstance(kunde, dict, "kunde_suchen_nach_id muss ein Dict zurückgeben!")
self.assertEqual(kunde["id"], id_neu)
self.assertEqual(kunde["name"], "Clara Oswald")
self.assertEqual(kunde["email"], "clara@tardis.org")
self.assertAlmostEqual(kunde["guthaben"], 250.0, places=2)
# Nicht existierender Kunde
unbekannt = self.db.kunde_suchen_nach_id(9999)
self.assertIsNone(unbekannt, "kunde_suchen_nach_id muss None zurückgeben, wenn ID nicht existiert!")
def test_04_guthaben_aufladen(self):
"""Prüft das Aufladen von Guthaben und Validierung von Beträgen."""
kid = self.db.kunde_hinzufuegen("David Miller", "david@web.de", 100.0)
# Gültiges Aufladen
erfolg = self.db.guthaben_aufladen(kid, 50.0)
self.assertTrue(erfolg, "guthaben_aufladen sollte True zurückgeben bei erfolgreicher Aktualisierung!")
kunde = self.db.kunde_suchen_nach_id(kid)
self.assertAlmostEqual(kunde["guthaben"], 150.0, places=2)
# Ungültiger Betrag (<= 0)
erfolg_negativ = self.db.guthaben_aufladen(kid, -20.0)
self.assertFalse(erfolg_negativ, "guthaben_aufladen darf bei Beträgen <= 0 keine Buchung durchführen (False)!")
erfolg_null = self.db.guthaben_aufladen(kid, 0.0)
self.assertFalse(erfolg_null, "guthaben_aufladen darf bei Betrag 0.0 nicht erfolgreich sein!")
# Nicht-existierende Kunden-ID
erfolg_fremd = self.db.guthaben_aufladen(8888, 50.0)
self.assertFalse(erfolg_fremd, "guthaben_aufladen sollte False zurückgeben, wenn Kunden-ID nicht existiert!")
def test_05_kunden_mit_mindestguthaben(self):
"""Prüft die Abfrage und Sortierung von Kunden nach Mindestguthaben."""
self.db.kunde_hinzufuegen("Sparfuchs", "sparfuchs@bank.de", 10.0)
self.db.kunde_hinzufuegen("Mittelstand", "mittel@bank.de", 500.0)
self.db.kunde_hinzufuegen("Vip Kunde", "vip@bank.de", 1200.0)
self.db.kunde_hinzufuegen("Grossanleger", "investor@bank.de", 2500.0)
# Filter ab 500.0
ergebnis = self.db.kunden_mit_mindestguthaben(500.0)
self.assertIsInstance(ergebnis, list, "kunden_mit_mindestguthaben muss eine Liste zurückgeben!")
self.assertEqual(len(ergebnis), 3, "Es sollten genau 3 Kunden >= 500€ Guthaben haben!")
# Prüfung der absteigenden Sortierung (Grossanleger > Vip Kunde > Mittelstand)
self.assertEqual(ergebnis[0]["name"], "Grossanleger")
self.assertEqual(ergebnis[1]["name"], "Vip Kunde")
self.assertEqual(ergebnis[2]["name"], "Mittelstand")
# Zu hohe Schwelle -> leere Liste
keine = self.db.kunden_mit_mindestguthaben(10000.0)
self.assertEqual(keine, [], "Sollte leere Liste zurückgeben, wenn kein Kunde die Schwelle erreicht!")
def test_06_kunde_loeschen(self):
"""Prüft das Löschen von Kunden aus der Datenbank."""
kid = self.db.kunde_hinzufuegen("Ex-Kunde", "ex@alt.de", 0.0)
loesch_erfolg = self.db.kunde_loeschen(kid)
self.assertTrue(loesch_erfolg, "kunde_loeschen sollte True zurückgeben, wenn Kunde existierte!")
# Erneute Suche muss None liefern
kunde = self.db.kunde_suchen_nach_id(kid)
self.assertIsNone(kunde, "Gelöschter Kunde darf über kunde_suchen_nach_id nicht mehr gefunden werden!")
# Zweites Löschen derselben ID muss False liefern
loesch_nochmal = self.db.kunde_loeschen(kid)
self.assertFalse(loesch_nochmal, "kunde_loeschen sollte False liefern, wenn ID nicht existiert!")
def test_07_sql_injection_schutz(self):
"""Prüft, ob parametrisierte Queries vor böswilligen SQL-Injections schützen."""
boeser_name = "Robert'); DROP TABLE kunden;--"
kid = self.db.kunde_hinzufuegen(boeser_name, "bobby@tables.com", 100.0)
kunde = self.db.kunde_suchen_nach_id(kid)
self.assertIsNotNone(kunde, "Kunde mit SQL-Injection-String im Namen sollte als regulärer Text gespeichert werden!")
self.assertEqual(kunde["name"], boeser_name)
# Tabelle 'kunden' muss intakt sein!
c = self.db.conn.cursor()
c.execute("SELECT COUNT(*) FROM kunden")
self.assertEqual(c.fetchone()[0], 1, "Tabelle 'kunden' wurde trotz SQL-Injection-Versuch nicht zerstört!")
if __name__ == "__main__":
res = unittest.main(verbosity=2, exit=False)
try:
root_dir = Path(__file__).parent.parent.parent.resolve()
sys.path.insert(0, str(root_dir))
from gamification import report_single_chapter_result
rel_pfad = f"{Path(__file__).parent.parent.name}/{Path(__file__).parent.name}"
report_single_chapter_result(rel_pfad, res.result.wasSuccessful(), res.result.testsRun)
except Exception:
pass
sys.exit(0 if res.result.wasSuccessful() else 1)

View file

@ -0,0 +1,48 @@
# Kapitel 22: Web-APIs, REST & JSON-Feeds 🌐📡
In diesem Kapitel lernst du, wie moderne Web-Anwendungen über REST-APIs kommunizieren, wie du strukturierte JSON-Feeds in Python parst, verschachtelte Daten extrahierst und Fehler robust abfängst.
---
## 🎯 Was du lernst
1. **Was ist eine REST-API?**
- Client-Server-Architektur im Web: Der Client stellt eine Anfrage (**Request**) per HTTP GET/POST an den Endpunkt, der Server antwortet (**Response**).
2. **Die Kellner-Analogie:**
- **Gast (Client/Python-Skript):** Möchte Daten (z.B. Wetterbericht oder Aktienkurse) haben.
- **Kellner (API):** Nimmt die Bestellung entgegen, bringt sie in die Küche und serviert die Antwort.
- **Küche/Koch (Server/Datenbank):** Verarbeitet die Anfrage und bereitet die Rohdaten zu.
- **Speisekarte (API-Dokumentation):** Beschreibt, welche Endpunkte und Parameter erlaubt sind.
3. **JSON als Universalsprache des Internets:**
- `json.loads(json_string)`: Wandelt einen empfangenen JSON-Text in ein Python-Dictionary bzw. eine Python-Liste um.
- `json.dumps(obj, indent=2)`: Wandelt ein Python-Objekt in einen JSON-formatierten String um.
4. **HTTP Status-Codes:**
- `200 OK`: Anfrage erfolgreich beantwortet.
- `400 Bad Request`: Fehlerhafte Anfrage / ungültige Parameter.
- `404 Not Found`: Endpunkt oder Ressource nicht gefunden.
- `500 Internal Server Error`: Unerwarteter Serverfehler.
5. **Robuste Fehlerbehandlung & Defensives Parsen:**
- `json.JSONDecodeError`: Wenn der Server HTML statt JSON sendet oder der Payload unvollständig ist.
- `KeyError` / `IndexError`: Wenn erwartete Felder im JSON fehlen.
- Sicheres Auslesen mit `.get()` und Standardwerten.
6. **100% Offline- & Pyodide-kompatibel:**
- Alle Übungen und Tests nutzen realistische Mock-JSON-Strings, sodass keine Internetverbindung oder externe API-Keys benötigt werden.
---
## 📁 Die Dateien in diesem Ordner
- **`index.html`**: Die interaktive Lernseite mit Kellner-Analogie, API-Ablaufdiagramm, JSON Cheat-Sheet und Subgoals.
- **`aufgabe.py`**: Dein Arbeitsblatt mit Parser-Funktionen und der Klasse `WetterApiParser`.
- **`test_aufgabe.py`**: Automatische Unittest-Suite (`python3 test_aufgabe.py`).
- **`musterloesung.py`**: Vollständig ausprogrammierte Beispiellösung.
---
## 🚀 Schnellstart
```bash
# 1. Bearbeite aufgabe.py
# 2. Teste deine Lösung:
python3 test_aufgabe.py
```

View file

@ -0,0 +1,222 @@
"""
Kapitel 22: Web-APIs, REST & JSON-Feeds 🌐📡
============================================
Lehrpfad 4: Professional Data Engineering
Thema:
REST-APIs, JSON als Datenformat, strukturierte Extraktion verschachtelter Payloads
und defensive Fehlerbehandlung (KeyError, JSONDecodeError).
Lernziele:
1. JSON-Strings parsen mit `json.loads(text)` und erzeugen mit `json.dumps(obj)`.
2. Extrahieren von Werten aus mehrfach verschachtelten Dictionaries und Listen.
3. Filtern und Transformieren von Web-Feeds (z.B. Aktienkurse, News-Ticker).
4. Validieren von HTTP-Statuscodes (200 OK vs. 404/500) und Fehler-Payloads.
5. Kapselung der API-Logik in einer wiederverwendbaren Klasse `WetterApiParser`.
"""
import json
from typing import List, Dict, Any, Tuple, Optional, Union
# ==============================================================================
# 🎯 TEILZIEL 1 (TODO 1): Wetter-Antwort parsen
# ==============================================================================
def parse_wetter_antwort(json_text: str) -> Dict[str, Any]:
"""
Parst den JSON-String einer Wetter-API (z.B. OpenWeatherMap-Format)
und extrahiert die wichtigsten Kenngrößen in ein kompaktes Dictionary.
Erwartete Eingabe-Struktur (Beispiel):
{
"name": "Wien",
"main": {
"temp": 21.5,
"humidity": 65
},
"weather": [
{
"main": "Clouds",
"description": "Leicht bewölkt"
}
]
}
Rückgabe-Format:
{
"stadt": str, # aus data["name"]
"temperatur": float, # aus data["main"]["temp"]
"luftfeuchtigkeit": int, # aus data["main"]["humidity"]
"wetterlage": str # aus data["weather"][0]["description"] (oder data["weather"][0]["main"])
}
Fehlerbehandlung:
- Wenn json_text ungültiges JSON ist (json.JSONDecodeError)
oder erwartete Schlüssel fehlen (KeyError, IndexError, TypeError):
Wirf einen ValueError("Ungültige Wetterdaten: " + str(e))
"""
# TODO: Implementieren
pass
# ==============================================================================
# 🎯 TEILZIEL 2 (TODO 2): Aktienkurse aus JSON-Feed filtern
# ==============================================================================
def filtriere_aktien_kurse(json_feed_text: str, min_kurs: float) -> List[Dict[str, Any]]:
"""
Parst einen Finanzmarkt-Feed und filtert alle Aktien heraus, deren Kurs
größer oder gleich `min_kurs` ist.
Akzeptiert Formate:
1. {"aktien": [{"symbol": "AAPL", "kurs": 185.5, "waehrung": "USD"}, ...]}
2. oder direkt eine Liste: [{"symbol": "AAPL", "kurs": 185.5}, ...]
Rückgabewert:
- Eine Liste von Dictionaries der gefilterten Aktien (z.B. [ {"symbol": "AAPL", "kurs": 185.5, ...}, ... ])
- Bei ungültigem JSON oder leerem Feed: leere Liste [] zurückgeben.
"""
# TODO: Implementieren
pass
# ==============================================================================
# 🎯 TEILZIEL 3 (TODO 3): Schlagzeilen-Ticker formatieren
# ==============================================================================
def formatiere_nachrichten_ticker(news_json_text: str) -> str:
"""
Parst einen News-JSON-Feed und formatiert die Schlagzeilen zu einem durchgehenden
Laufband-String für einen Nachrichtenticker.
Erwartete Eingabe-Struktur:
{
"nachrichten": [
{"titel": "Python 3.13 veröffentlicht", "ressort": "Tech"},
{"titel": "DAX schließt im Plus", "ressort": "Wirtschaft"}
]
}
Formatierungsregel:
- Jede Nachricht wird formatiert als: "[<Ressort>] <Titel> +++ "
- Beispiel-Ergebnis:
"[Tech] Python 3.13 veröffentlicht +++ [Wirtschaft] DAX schließt im Plus +++"
- Falls keine Nachrichten vorhanden sind oder die Liste leer ist:
"+++ Keine aktuellen Eilmeldungen +++" zurückgeben.
- Bei ungültigem JSON: "+++ Fehler beim Laden des Nachrichten-Feeds +++" zurückgeben.
"""
# TODO: Implementieren
pass
# ==============================================================================
# 🎯 TEILZIEL 4 (TODO 4): HTTP-Antwort & Statuscode validieren
# ==============================================================================
def validiere_api_antwort(status_code: int, payload_json_text: str) -> Tuple[bool, Union[Dict[str, Any], str]]:
"""
Überprüft den HTTP-Statuscode und den JSON-Payload einer Web-API-Antwort.
Regeln:
1. Wenn status_code == 200:
- Versuche payload_json_text mit json.loads() zu parsen.
- Bei Erfolg: Gib (True, geparstes_dict) zurück.
- Bei json.JSONDecodeError: Gib (False, "Ungültiges JSON-Format") zurück.
2. Wenn status_code != 200 (z.B. 400, 404, 500):
- Versuche aus dem JSON-Payload eine Fehlermeldung zu extrahieren (Schlüssel 'error' oder 'message').
- Falls vorhanden: Gib (False, f"HTTP Fehler {status_code}: {fehlermeldung}") zurück.
- Falls nicht parsebar oder kein Schlüssel: Gib (False, f"HTTP Fehler {status_code}") zurück.
"""
# TODO: Implementieren
pass
# ==============================================================================
# 🎯 TEILZIEL 5 (TODO 5): Klasse WetterApiParser
# ==============================================================================
class WetterApiParser:
"""
Objektorientierter Wrapper für Wetterdaten aus einer REST-API.
"""
def __init__(self, raw_json: str = ""):
"""
Initialisiert den Parser.
Falls raw_json übergeben wurde, wird sofort lade_daten() ausgeführt.
"""
self.raw_json: str = raw_json
self.daten: Optional[Dict[str, Any]] = None
if raw_json:
self.lade_daten(raw_json)
def lade_daten(self, raw_json: str) -> bool:
"""
Versucht den JSON-String mittels parse_wetter_antwort() einzulesen.
- Bei Erfolg: Speichert das extrahierte Dict in self.daten und gibt True zurück.
- Bei Fehler (ValueError): Setzt self.daten = None und gibt False zurück.
"""
# TODO: Implementieren
pass
def get_temperatur(self) -> Optional[float]:
"""Gibt die Temperatur zurück oder None, wenn keine Daten geladen sind."""
# TODO: Implementieren
pass
def get_stadt(self) -> Optional[str]:
"""Gibt den Stadtnamen zurück oder None, wenn keine Daten geladen sind."""
# TODO: Implementieren
pass
def ist_frostig(self) -> bool:
"""Gibt True zurück, wenn die Temperatur unter 0.0 °C liegt, sonst False."""
# TODO: Implementieren
pass
def ist_gueltig(self) -> bool:
"""Gibt True zurück, wenn gültige Wetterdaten geladen sind (self.daten is not None)."""
# TODO: Implementieren
pass
# ==============================================================================
# Interaktiver Test im Terminal:
# (python3 aufgabe.py)
# ==============================================================================
if __name__ == "__main__":
beispiel_wetter = """
{
"name": "München",
"main": {"temp": 18.2, "humidity": 72},
"weather": [{"main": "Rain", "description": "Leichter Regen"}]
}
"""
print("🌤️ Teste parse_wetter_antwort():")
wetter = parse_wetter_antwort(beispiel_wetter)
print(wetter)
beispiel_aktien = """
{
"aktien": [
{"symbol": "AAPL", "kurs": 189.5, "waehrung": "USD"},
{"symbol": "GOOGL", "kurs": 142.0, "waehrung": "USD"},
{"symbol": "PENNY", "kurs": 0.45, "waehrung": "USD"}
]
}
"""
print("\n📈 Teste filtriere_aktien_kurse() (min_kurs = 100):")
gefiltert = filtriere_aktien_kurse(beispiel_aktien, 100.0)
print(gefiltert)
beispiel_news = """
{
"nachrichten": [
{"titel": "Python 3.13 bringt Performance-Schub", "ressort": "Technologie"},
{"titel": "Mars-Rover findet neue Gesteinsproben", "ressort": "Wissenschaft"}
]
}
"""
print("\n📰 Teste formatiere_nachrichten_ticker():")
print(formatiere_nachrichten_ticker(beispiel_news))
print("\n🛡️ Teste validiere_api_antwort():")
print("Status 200:", validiere_api_antwort(200, '{"status": "ok", "items": [1, 2, 3]}'))
print("Status 404:", validiere_api_antwort(404, '{"error": "Ressource nicht gefunden"}'))

View file

@ -0,0 +1,443 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kapitel 22: Web-APIs, REST &amp; JSON-Feeds</title>
<link rel="stylesheet" href="../../assets/style.css">
</head>
<body>
<div class="app-layout">
<aside class="app-sidebar">
<div class="sidebar-header">
<a href="../../index.html" class="sidebar-brand-link">
<div class="sidebar-logo">🐍</div>
<div>
<div class="sidebar-title">Python Portal</div>
<div class="sidebar-subtitle">Lernplattform</div>
</div>
</a>
</div>
<div class="sidebar-quick-nav">
<a href="../../index.html" class="sidebar-link">🏠 Startseite</a>
<a href="../../index.html#tutorial" class="sidebar-link">📖 Tutorial &amp; Workflow</a>
<a href="../../index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a>
</div>
<div class="sidebar-nav">
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🌱 1. Grundlagen</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">G01–G10</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_1_grundlagen/01_erste_schritte_taschenrechner/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧮</span>
<span class="sidebar-link-text">G01. Taschenrechner</span>
</a>
<a href="../../lehrpfad_1_grundlagen/02_variablen_und_datentypen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📦</span>
<span class="sidebar-link-text">G02. Variablen &amp; Typen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/03_ein_und_ausgabe/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💬</span>
<span class="sidebar-link-text">G03. Ein- &amp; Ausgabe</span>
</a>
<a href="../../lehrpfad_1_grundlagen/04_verzweigungen_und_bedingungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚦</span>
<span class="sidebar-link-text">G04. Bedingungen (if)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/05_schleifen_und_wiederholungen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎡</span>
<span class="sidebar-link-text">G05. Schleifen (for/while)</span>
</a>
<a href="../../lehrpfad_1_grundlagen/06_funktionen_und_module/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🪄</span>
<span class="sidebar-link-text">G06. Funktionen &amp; Module</span>
</a>
<a href="../../lehrpfad_1_grundlagen/07_listen_und_sequenzen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">📋</span>
<span class="sidebar-link-text">G07. Listen &amp; Sequenzen</span>
</a>
<a href="../../lehrpfad_1_grundlagen/08_textverarbeitung_und_strings/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔤</span>
<span class="sidebar-link-text">G08. String-Methoden</span>
</a>
<a href="../../lehrpfad_1_grundlagen/09_dictionaries_und_sets/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗃️</span>
<span class="sidebar-link-text">G09. Dicts &amp; Sets</span>
</a>
<a href="../../lehrpfad_1_grundlagen/10_comprehensions_datum_algorithmen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🚀</span>
<span class="sidebar-link-text">G10. Comprehensions</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🏗️ 2. OOP Einstieg</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">00–06</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_2_oop_einstieg/00_fehlersuche_und_grundlagen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🔍</span>
<span class="sidebar-link-text">00. Fehlersuche &amp; Debug</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/01_einstieg_klassen/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏗️</span>
<span class="sidebar-link-text">01. Einstieg in Klassen</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/02_init_und_self/index.html" class="sidebar-link">
<span class="sidebar-link-icon">⚙️</span>
<span class="sidebar-link-text">02. Konstruktor &amp; self</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/03_methoden_und_verhalten/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏎️</span>
<span class="sidebar-link-text">03. Methoden &amp; Verhalten</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04_str_und_darstellung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧾</span>
<span class="sidebar-link-text">04. __str__ Darstellung</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04b_umstieg_vscode/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💻</span>
<span class="sidebar-link-text">04b. VS Code Setup</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/04c_git_und_versionskontrolle/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🌿</span>
<span class="sidebar-link-text">04c. Git-Versionskontrolle</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/05_objekte_kombinieren/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎵</span>
<span class="sidebar-link-text">05. Objekte kombinieren</span>
</a>
<a href="../../lehrpfad_2_oop_einstieg/06_abschlussprojekt_tamagotchi/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🥚</span>
<span class="sidebar-link-text">06. Projekt: Tamagotchi</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>🚀 3. Fortgeschritten</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">07–16</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../../lehrpfad_3_fortgeschrittenes_oop/07_referenzen_und_speicher/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧠</span>
<span class="sidebar-link-text">07. Referenzen &amp; Speicher</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/08_operator_overloading_dunder/index.html" class="sidebar-link">
<span class="sidebar-link-icon">➕</span>
<span class="sidebar-link-text">08. Operator Overloading</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/09_eigene_unit_tests_schreiben/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧪</span>
<span class="sidebar-link-text">09. Unit Tests &amp; TDD</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/10_vererbung_und_super/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🧬</span>
<span class="sidebar-link-text">10. Vererbung &amp; super()</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/11_polymorphie_und_interfaces/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎭</span>
<span class="sidebar-link-text">11. Polymorphie</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/12_exceptions_und_fehlerbehandlung/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🛡️</span>
<span class="sidebar-link-text">12. Exceptions &amp; Fehler</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/13_persistenz_json_und_csv/index.html" class="sidebar-link">
<span class="sidebar-link-icon">💾</span>
<span class="sidebar-link-text">13. Persistenz (JSON/CSV)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/14_gui_mit_tkinter/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🖥️</span>
<span class="sidebar-link-text">14. Desktop-GUIs (Tkinter)</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/15_parameter_und_container/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🎒</span>
<span class="sidebar-link-text">15. Container &amp; *args</span>
</a>
<a href="../../lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🏆</span>
<span class="sidebar-link-text">16. Master-Abschlussprojekt</span>
</a>
</div>
</div>
<div class="sidebar-group">
<div class="sidebar-group-title">
<span>📊 4. Data Engineering</span>
<span style="background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 99px; font-size: 0.7rem;">21–22</span>
</div>
<div style="display: flex; flex-direction: column; gap: 2px;">
<a href="../21_datenbanken_und_sqlite/index.html" class="sidebar-link">
<span class="sidebar-link-icon">🗄️</span>
<span class="sidebar-link-text">21. Datenbanken &amp; SQLite</span>
</a>
<a href="../22_web_apis_und_json_feeds/index.html" class="sidebar-link active">
<span class="sidebar-link-icon">🌐</span>
<span class="sidebar-link-text">22. Web-APIs &amp; JSON</span>
</a>
</div>
</div>
</div>
<div class="sidebar-footer">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="font-size: 0.85rem; font-weight: 700; color: white;" id="sidebar-level-text">⭐ Level 1</div>
<div style="font-size: 0.82rem; font-weight: 700; color: #38bdf8;" id="sidebar-xp-text">0 XP</div>
</div>
</div>
</aside>
<div class="app-main">
<header>
<div class="header-container">
<nav class="nav-links">
<a href="../../index.html">🏠 Zur Kursübersicht</a>
<span>&rsaquo;</span>
<span>Lehrpfad 4</span>
<span>&rsaquo;</span>
<a href="../21_datenbanken_und_sqlite/index.html">Kapitel 21</a>
<span>&rsaquo;</span>
<span style="color: white; font-weight: 600;">Kapitel 22</span>
</nav>
<div class="header-badge">Kapitel 22 &bull; Data Engineering &bull; Web &amp; REST APIs</div>
<h1>Web-APIs, REST &amp; JSON-Feeds 🌐📡</h1>
<p>Lerne, wie Web-APIs funktionieren, wie du strukturierte JSON-Feeds in Python parst, Daten filterst und Netzwerk- sowie Parsing-Fehler professionell abfängst.</p>
</div>
</header>
<main class="container">
<!-- 📂 Schnellzugriff & Download aller Dateien dieser Einheit -->
<div style="background: #ffffff; border: 1px solid var(--border-color); border-radius: var(--radius-md); padding: 14px 20px; margin-bottom: 25px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; box-shadow: var(--shadow-sm);">
<div style="font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 8px;">
<span>📂 Dateien dieser Einheit:</span>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="aufgabe.py" class="btn" style="padding: 6px 14px; font-size: 0.88rem; background: var(--primary);" download>📄 aufgabe.py (Startercode)</a>
<a href="test_aufgabe.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>🧪 test_aufgabe.py (Tests)</a>
<a href="musterloesung.py" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;" download>💡 musterloesung.py (Lösung)</a>
<a href="README.md" class="btn btn-secondary" style="padding: 6px 14px; font-size: 0.88rem;">📖 README.md</a>
</div>
</div>
<!-- 1. Was ist eine REST-API? Analogie -->
<section class="card">
<h2>1. Was ist eine REST-API? (Die Kellner-Analogie)</h2>
<p>
Eine <strong>API (Application Programming Interface)</strong> ist eine Schnittstelle, über die zwei Computer oder Programme übers Internet miteinander kommunizieren.
Nahezu jede moderne Web-App (wie Wetterberichte, Börsenkurse, Spotify oder Google Maps) stellt ihre Daten über REST-APIs im <strong>JSON-Format</strong> bereit.
</p>
<div class="box box-analogie">
<div class="box-title">🍽️ Die Restaurant-Analogie:</div>
Stell dir den Ablauf in einem Restaurant vor:
<ul style="margin-top: 8px; margin-left: 20px;">
<li><strong>Der Gast (Client / Dein Python-Skript):</strong> Du sitzt am Tisch und möchtest ein bestimmtes Gericht (Daten) bestellen.</li>
<li><strong>Die Speisekarte (API-Dokumentation / Endpunkte):</strong> Zeigt dir, welche Gerichte (URLs &amp; Parameter wie <code>/weather?city=Wien</code>) du bestellen kannst.</li>
<li><strong>Der Kellner (Die REST-API):</strong> Nimmt deine Bestellung (**HTTP Request**) entgegen, eilt in die Küche und bringt dir das fertige Essen (**HTTP Response**) zurück.</li>
<li><strong>Die Küche &amp; Vorratskammer (Web-Server &amp; Datenbank):</strong> Verarbeitet die Rohzutaten und bereitet das Essen zu.</li>
</ul>
</div>
<h3>Der typische Ablauf eines API-Aufrufs</h3>
<div style="background: #0b0f19; border-radius: var(--radius-md); padding: 18px; color: #f8fafc; font-family: var(--font-mono); font-size: 0.9rem; line-height: 1.8; margin: 15px 0;">
<div style="color: #38bdf8;">[Client: Python-App]</div>
<div style="padding-left: 20px; color: #a5b4fc;">│ &nbsp;🚀 1. HTTP GET Request (z.B. https://api.wetter.com/v1/forecast?city=Berlin)</div>
<div style="padding-left: 20px; color: #64748b;">▼</div>
<div style="color: #34d399;">[Server: Wetter-Datenbank]</div>
<div style="padding-left: 20px; color: #fbbf24;">│ &nbsp;📦 2. HTTP Response 200 OK + JSON-Payload: {"temp": 21.5, "city": "Berlin"}</div>
<div style="padding-left: 20px; color: #64748b;">▼</div>
<div style="color: #38bdf8;">[Client: json.loads(payload)] ➔ Extrahiert Dicts, Listen und bereitet Anzeige auf</div>
</div>
</section>
<!-- 2. HTTP Statuscodes -->
<section class="card">
<h2>2. Die wichtigsten HTTP-Statuscodes</h2>
<p>Jede API-Antwort enthält einen 3-stelligen numerischen Statuscode, der signalisiert, ob die Anfrage geklappt hat:</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);">Code</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Bedeutung</th>
<th style="padding: 10px; border: 1px solid var(--border-color);">Typischer Grund</th>
</tr>
</thead>
<tbody>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><span style="color: #059669; font-weight: 700;">200 OK</span></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Erfolg</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Die Daten wurden erfolgreich gefunden und als JSON zurückgegeben.</td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><span style="color: #d97706; font-weight: 700;">400 Bad Request</span></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Ungültige Anfrage</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Falsche Parameter übergeben (z.B. ungültiges Datumsformat).</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid var(--border-color);"><span style="color: #e11d48; font-weight: 700;">404 Not Found</span></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Nicht gefunden</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Stadt, Benutzer oder URL-Endpunkt existiert nicht.</td>
</tr>
<tr style="background: #fafafa;">
<td style="padding: 10px; border: 1px solid var(--border-color);"><span style="color: #e11d48; font-weight: 700;">500 Internal Server Error</span></td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Serverfehler</td>
<td style="padding: 10px; border: 1px solid var(--border-color);">Unerwarteter Fehler oder Absturz auf dem Zielserver.</td>
</tr>
</tbody>
</table>
</section>
<!-- 3. JSON Parsing & Defensive Programmierung -->
<section class="card">
<h2>3. JSON-Parsing &amp; Defensive Fehlerbehandlung</h2>
<p>
Im Web empfangen wir Daten fast immer als rohen Textstring. Mit <code>json.loads()</code> (load string) parsen wir ihn in Python:
</p>
<div class="code-container">
<div class="code-header">
<span>Sicheres Parsen verschachtelter JSON-Antworten</span>
</div>
<pre><span class="kw">import</span> <span class="var">json</span>
<span class="var">roher_json_text</span> = <span class="str">'''
{
"name": "Hamburg",
"main": {"temp": 17.5, "humidity": 82},
"weather": [{"description": "Leichter Nieselregen"}]
}
'''</span>
<span class="kw">try</span>:
<span class="var">daten</span> = <span class="var">json</span>.<span class="fn">loads</span>(<span class="var">roher_json_text</span>)
<span class="cm"># Defensiver Zugriff auf verschachtelte Werte:</span>
<span class="var">stadt</span> = <span class="var">daten</span>[<span class="str">"name"</span>]
<span class="var">temperatur</span> = <span class="fn">float</span>(<span class="var">daten</span>[<span class="str">"main"</span>][<span class="str">"temp"</span>])
<span class="var">beschreibung</span> = <span class="var">daten</span>[<span class="str">"weather"</span>][<span class="num">0</span>][<span class="str">"description"</span>]
<span class="kw">except</span> <span class="var">json</span>.<span class="var">JSONDecodeError</span> <span class="kw">as</span> <span class="var">e</span>:
<span class="fn">print</span>(<span class="str">f"Kaputter JSON-String: {e}"</span>)
<span class="kw">except</span> (<span class="var">KeyError</span>, <span class="var">IndexError</span>) <span class="kw">as</span> <span class="var">e</span>:
<span class="fn">print</span>(<span class="str">f"Unerwartete Datenstruktur / Fehlendes Feld: {e}"</span>)</pre>
</div>
</section>
<!-- 4. Aufgabenstellung -->
<section class="card">
<h2>4. 🎯 Deine Aufgaben in <code><a href="aufgabe.py" style="color: inherit; text-decoration: underline;">aufgabe.py</a></code></h2>
<p>Öffne die Datei <code><a href="aufgabe.py" style="color: inherit; text-decoration: underline;">aufgabe.py</a></code> und vervollständige die Web-API-Tools:</p>
<ul class="task-list">
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t22_1">
<label for="t22_1" class="task-label">
<strong>TODO 1 (parse_wetter_antwort):</strong> Extrahiere <code>stadt</code>, <code>temperatur</code>, <code>luftfeuchtigkeit</code> und <code>wetterlage</code>. Fange <code>JSONDecodeError</code>, <code>KeyError</code> ab und wirf <code>ValueError</code> bei fehlerhaften Payloads.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t22_2">
<label for="t22_2" class="task-label">
<strong>TODO 2 (filtriere_aktien_kurse):</strong> Parst einen JSON-Feed von Aktien und filtert alle Aktien mit <code>kurs &gt;= min_kurs</code> heraus.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t22_3">
<label for="t22_3" class="task-label">
<strong>TODO 3 (formatiere_nachrichten_ticker):</strong> Formatiere Schlagzeilen zu einem durchgehenden Ticker-String <code>[Ressort] Titel +++</code>.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t22_4">
<label for="t22_4" class="task-label">
<strong>TODO 4 (validiere_api_antwort):</strong> Prüfe Statuscode 200 vs. Fehlercodes (400, 404, 500) und gib ein Tupel <code>(erfolg, daten_oder_nachricht)</code> zurück.
</label>
</li>
<li class="task-item">
<input type="checkbox" class="task-checkbox" id="t22_5">
<label for="t22_5" class="task-label">
<strong>TODO 5 (Klasse WetterApiParser):</strong> Implementiere die OOP-Klasse mit <code>lade_daten()</code>, <code>get_temperatur()</code>, <code>get_stadt()</code>, <code>ist_frostig()</code> und <code>ist_gueltig()</code>.
</label>
</li>
</ul>
<h3>💡 Lösungshinweise (Aufklappen bei Bedarf)</h3>
<details>
<summary>🔍 Tipp zu parse_wetter_antwort</summary>
<div class="details-content">
<div class="code-container">
<pre><span class="kw">try</span>:
<span class="var">data</span> = <span class="var">json</span>.<span class="fn">loads</span>(<span class="var">json_text</span>)
<span class="kw">return</span> {
<span class="str">"stadt"</span>: <span class="var">data</span>[<span class="str">"name"</span>],
<span class="str">"temperatur"</span>: <span class="fn">float</span>(<span class="var">data</span>[<span class="str">"main"</span>][<span class="str">"temp"</span>]),
<span class="str">"luftfeuchtigkeit"</span>: <span class="fn">int</span>(<span class="var">data</span>[<span class="str">"main"</span>][<span class="str">"humidity"</span>]),
<span class="str">"wetterlage"</span>: <span class="var">data</span>[<span class="str">"weather"</span>][<span class="num">0</span>][<span class="str">"description"</span>]
}
<span class="kw">except</span> <span class="var">Exception</span> <span class="kw">as</span> <span class="var">e</span>:
<span class="kw">raise</span> <span class="var">ValueError</span>(<span class="str">f"Ungültige Wetterdaten: {e}"</span>)</pre>
</div>
</div>
</details>
<details>
<summary>🔍 Tipp zum Ticker-String</summary>
<div class="details-content">
<div class="code-container">
<pre><span class="var">items</span> = [<span class="str">f"[{n['ressort']}] {n['titel']}"</span> <span class="kw">for</span> <span class="var">n</span> <span class="kw">in</span> <span class="var">nachrichten</span>]
<span class="kw">return</span> <span class="str">" +++ "</span>.<span class="fn">join</span>(<span class="var">items</span>) + <span class="str">" +++"</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>22_web_apis_und_json_feeds</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="../21_datenbanken_und_sqlite/index.html" class="btn btn-secondary">&larr; Zurück zu Kapitel 21</a>
<a href="../../index.html" class="btn">🏠 Zurück zur Kursübersicht</a>
</div>
</section>
</main>
</div>
</div>
<button type="button" class="sidebar-mobile-toggle"><span>☰ Menü</span></button>
<div class="sidebar-overlay"></div>
<!-- Scripts -->
<script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script>
<script src="../../assets/interpreter.js"></script>
<script src="../../assets/certificate.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/runner.js"></script>
<script src="../../assets/ui_enhancements.js"></script>
<script src="../../assets/sidebar.js"></script>
</body>
</html>

View file

@ -0,0 +1,166 @@
"""
Kapitel 22: Web-APIs, REST & JSON-Feeds 🌐📡
============================================
Lehrpfad 4: Professional Data Engineering
Musterlösung für Web-APIs, JSON-Feeds und defensive Fehlerbehandlung.
"""
import json
from typing import List, Dict, Any, Tuple, Optional, Union
def parse_wetter_antwort(json_text: str) -> Dict[str, Any]:
"""
Parst den JSON-String einer Wetter-API und extrahiert Stadt, Temperatur,
Luftfeuchtigkeit und Wetterlage.
"""
try:
data = json.loads(json_text)
stadt = data["name"]
temperatur = float(data["main"]["temp"])
luftfeuchtigkeit = int(data["main"]["humidity"])
weather_list = data["weather"]
if not weather_list:
wetterlage = "Unbekannt"
else:
wetterlage = weather_list[0].get("description") or weather_list[0].get("main", "Unbekannt")
return {
"stadt": stadt,
"temperatur": temperatur,
"luftfeuchtigkeit": luftfeuchtigkeit,
"wetterlage": str(wetterlage),
}
except (json.JSONDecodeError, KeyError, IndexError, TypeError, ValueError) as e:
raise ValueError(f"Ungültige Wetterdaten: {e}")
def filtriere_aktien_kurse(json_feed_text: str, min_kurs: float) -> List[Dict[str, Any]]:
"""
Filtert alle Aktien mit Kurs >= min_kurs heraus.
"""
try:
data = json.loads(json_feed_text)
if isinstance(data, dict) and "aktien" in data:
aktien_liste = data["aktien"]
elif isinstance(data, dict) and "stocks" in data:
aktien_liste = data["stocks"]
elif isinstance(data, list):
aktien_liste = data
else:
return []
ergebnis = []
for item in aktien_liste:
if isinstance(item, dict) and "kurs" in item:
if float(item["kurs"]) >= float(min_kurs):
ergebnis.append(item)
elif isinstance(item, dict) and "price" in item:
if float(item["price"]) >= float(min_kurs):
ergebnis.append(item)
return ergebnis
except (json.JSONDecodeError, TypeError, ValueError):
return []
def formatiere_nachrichten_ticker(news_json_text: str) -> str:
"""
Formatiert Schlagzeilen zu einem Ticker-String.
"""
try:
data = json.loads(news_json_text)
if isinstance(data, dict):
nachrichten = data.get("nachrichten") or data.get("articles") or []
elif isinstance(data, list):
nachrichten = data
else:
return "+++ Keine aktuellen Eilmeldungen +++"
if not nachrichten:
return "+++ Keine aktuellen Eilmeldungen +++"
eintraege = []
for item in nachrichten:
if isinstance(item, dict):
titel = item.get("titel") or item.get("title") or "Ohne Titel"
ressort = item.get("ressort") or item.get("category") or "Allgemein"
eintraege.append(f"[{ressort}] {titel}")
if not eintraege:
return "+++ Keine aktuellen Eilmeldungen +++"
return " +++ ".join(eintraege) + " +++"
except (json.JSONDecodeError, TypeError):
return "+++ Fehler beim Laden des Nachrichten-Feeds +++"
def validiere_api_antwort(status_code: int, payload_json_text: str) -> Tuple[bool, Union[Dict[str, Any], str]]:
"""
Prüft Statuscode und JSON-Payload einer API-Antwort.
"""
if status_code == 200:
try:
data = json.loads(payload_json_text)
return (True, data)
except json.JSONDecodeError:
return (False, "Ungültiges JSON-Format")
else:
# Fehlerstatuscode
error_detail = None
try:
err_data = json.loads(payload_json_text)
if isinstance(err_data, dict):
error_detail = err_data.get("error") or err_data.get("message") or err_data.get("detail")
except Exception:
pass
if error_detail:
return (False, f"HTTP Fehler {status_code}: {error_detail}")
return (False, f"HTTP Fehler {status_code}")
class WetterApiParser:
"""
Objektorientierter Wetter-Parser.
"""
def __init__(self, raw_json: str = ""):
self.raw_json: str = raw_json
self.daten: Optional[Dict[str, Any]] = None
if raw_json:
self.lade_daten(raw_json)
def lade_daten(self, raw_json: str) -> bool:
"""Lädt und validiert Wetter-JSON."""
self.raw_json = raw_json
try:
self.daten = parse_wetter_antwort(raw_json)
return True
except ValueError:
self.daten = None
return False
def get_temperatur(self) -> Optional[float]:
"""Liefert die Temperatur oder None."""
if self.daten is not None:
return self.daten.get("temperatur")
return None
def get_stadt(self) -> Optional[str]:
"""Liefert die Stadt oder None."""
if self.daten is not None:
return self.daten.get("stadt")
return None
def ist_frostig(self) -> bool:
"""Gibt True zurück wenn Temperatur < 0.0 °C."""
temp = self.get_temperatur()
if temp is not None:
return temp < 0.0
return False
def ist_gueltig(self) -> bool:
"""Prüft ob valide Daten vorliegen."""
return self.daten is not None

View file

@ -0,0 +1,180 @@
import unittest
import sys
import json
from pathlib import Path
# Sicherstellen, dass das aktuelle Verzeichnis im Python-Pfad liegt
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel22(unittest.TestCase):
"""
Umfassende Offline-Testsuite für Modul 22 (REST-APIs, JSON-Feeds & Fehlerbehandlung).
Nutzt realistische Mock-JSON-Datensätze (100% Pyodide- und Offline-kompatibel).
"""
def setUp(self):
self.parse_wetter_antwort = getattr(aufgabe, "parse_wetter_antwort", None)
self.filtriere_aktien_kurse = getattr(aufgabe, "filtriere_aktien_kurse", None)
self.formatiere_nachrichten_ticker = getattr(aufgabe, "formatiere_nachrichten_ticker", None)
self.validiere_api_antwort = getattr(aufgabe, "validiere_api_antwort", None)
self.WetterApiParser = getattr(aufgabe, "WetterApiParser", None)
self.assertIsNotNone(self.parse_wetter_antwort, "Funktion 'parse_wetter_antwort' fehlt!")
self.assertIsNotNone(self.filtriere_aktien_kurse, "Funktion 'filtriere_aktien_kurse' fehlt!")
self.assertIsNotNone(self.formatiere_nachrichten_ticker, "Funktion 'formatiere_nachrichten_ticker' fehlt!")
self.assertIsNotNone(self.validiere_api_antwort, "Funktion 'validiere_api_antwort' fehlt!")
self.assertIsNotNone(self.WetterApiParser, "Klasse 'WetterApiParser' fehlt!")
def test_01_parse_wetter_antwort_erfolg(self):
"""Prüft die Extraktion von Stadt, Temperatur, Luftfeuchtigkeit und Wetterlage."""
mock_payload = json.dumps({
"name": "Berlin",
"coord": {"lon": 13.41, "lat": 52.52},
"main": {
"temp": 19.4,
"feels_like": 18.9,
"humidity": 68
},
"weather": [
{
"id": 500,
"main": "Rain",
"description": "Mäßiger Regen",
"icon": "10d"
}
]
})
ergebnis = self.parse_wetter_antwort(mock_payload)
self.assertIsInstance(ergebnis, dict, "parse_wetter_antwort muss ein Dictionary zurückgeben!")
self.assertEqual(ergebnis["stadt"], "Berlin")
self.assertAlmostEqual(ergebnis["temperatur"], 19.4, places=1)
self.assertEqual(ergebnis["luftfeuchtigkeit"], 68)
self.assertEqual(ergebnis["wetterlage"], "Mäßiger Regen")
def test_02_parse_wetter_antwort_fehlerbehandlung(self):
"""Prüft, dass bei ungültigem JSON oder fehlenden Feldern ein ValueError ausgelöst wird."""
# 1. Korrupter JSON-String
with self.assertRaises(ValueError, msg="Ungültiger JSON-String muss einen ValueError werfen!"):
self.parse_wetter_antwort("{ungueltiges json: 42")
# 2. Fehlendes 'main' Objekt
unvollstaendig = json.dumps({"name": "Hamburg", "weather": [{"description": "Sonne"}]})
with self.assertRaises(ValueError, msg="Fehlende Pflichtfelder müssen einen ValueError werfen!"):
self.parse_wetter_antwort(unvollstaendig)
def test_03_filtriere_aktien_kurse(self):
"""Prüft das Filtern von Aktienkursen über dem Schwellenwert."""
feed = json.dumps({
"timestamp": "2026-08-29T12:00:00Z",
"aktien": [
{"symbol": "NVDA", "kurs": 125.40, "waehrung": "USD"},
{"symbol": "AAPL", "kurs": 220.10, "waehrung": "USD"},
{"symbol": "PENNY", "kurs": 1.20, "waehrung": "EUR"},
{"symbol": "MSFT", "kurs": 445.00, "waehrung": "USD"}
]
})
gefiltert = self.filtriere_aktien_kurse(feed, 200.0)
self.assertIsInstance(gefiltert, list)
self.assertEqual(len(gefiltert), 2, "Es sollten genau 2 Aktien >= 200.0 $ gefunden werden!")
symbole = [a["symbol"] for a in gefiltert]
self.assertIn("AAPL", symbole)
self.assertIn("MSFT", symbole)
self.assertNotIn("NVDA", symbole)
self.assertNotIn("PENNY", symbole)
# Ungültiges JSON liefert leere Liste
self.assertEqual(self.filtriere_aktien_kurse("defektes json", 100.0), [])
def test_04_formatiere_nachrichten_ticker(self):
"""Prüft die Formatierung der Schlagzeilen als durchgehender Ticker."""
feed = json.dumps({
"nachrichten": [
{"titel": "Python 3.13 veröffentlicht", "ressort": "Tech"},
{"titel": "DAX schließt im Plus", "ressort": "Wirtschaft"}
]
})
ticker = self.formatiere_nachrichten_ticker(feed)
self.assertIsInstance(ticker, str)
self.assertIn("[Tech] Python 3.13 veröffentlicht", ticker)
self.assertIn("[Wirtschaft] DAX schließt im Plus", ticker)
self.assertTrue(ticker.endswith("+++"))
# Leere Liste
leerer_feed = json.dumps({"nachrichten": []})
self.assertEqual(self.formatiere_nachrichten_ticker(leerer_feed), "+++ Keine aktuellen Eilmeldungen +++")
# Defektes JSON
self.assertEqual(self.formatiere_nachrichten_ticker("###"), "+++ Fehler beim Laden des Nachrichten-Feeds +++")
def test_05_validiere_api_antwort_200_ok(self):
"""Prüft erfolgreiche API-Antworten (Status 200)."""
payload = json.dumps({"status": "success", "user_id": 42, "role": "admin"})
status, daten = self.validiere_api_antwort(200, payload)
self.assertTrue(status, "Statuscode 200 mit gültigem JSON muss True liefern!")
self.assertIsInstance(daten, dict)
self.assertEqual(daten["user_id"], 42)
def test_06_validiere_api_antwort_200_bad_json(self):
"""Prüft Status 200 mit defektem JSON Payload (z.B. Verbindungsabbruch)."""
status, msg = self.validiere_api_antwort(200, "<html>502 Bad Gateway</html>")
self.assertFalse(status)
self.assertEqual(msg, "Ungültiges JSON-Format")
def test_07_validiere_api_antwort_fehler_status(self):
"""Prüft HTTP-Fehlercodes (404, 500) und das Extrahieren von Fehlermeldungen."""
# 404 mit Fehler-JSON
payload_404 = json.dumps({"error": "Benutzerprofil nicht gefunden", "code": 404})
status_404, msg_404 = self.validiere_api_antwort(404, payload_404)
self.assertFalse(status_404)
self.assertIn("404", msg_404)
self.assertIn("Benutzerprofil nicht gefunden", msg_404)
# 500 ohne JSON
status_500, msg_500 = self.validiere_api_antwort(500, "Internal Server Error")
self.assertFalse(status_500)
self.assertIn("500", msg_500)
def test_08_wetter_api_parser_klasse(self):
"""Prüft die objektorientierte Klasse WetterApiParser."""
gueltiges_json = json.dumps({
"name": "Innsbruck",
"main": {"temp": -3.5, "humidity": 80},
"weather": [{"main": "Snow", "description": "Schneefall"}]
})
parser = self.WetterApiParser(gueltiges_json)
self.assertTrue(parser.ist_gueltig())
self.assertEqual(parser.get_stadt(), "Innsbruck")
self.assertAlmostEqual(parser.get_temperatur(), -3.5, places=1)
self.assertTrue(parser.ist_frostig(), "-3.5 °C muss als frostig erkannt werden!")
# Ungültige Daten laden
parser.lade_daten("kaputtes json")
self.assertFalse(parser.ist_gueltig())
self.assertIsNone(parser.get_temperatur())
self.assertIsNone(parser.get_stadt())
self.assertFalse(parser.ist_frostig())
if __name__ == "__main__":
res = unittest.main(verbosity=2, exit=False)
try:
root_dir = Path(__file__).parent.parent.parent.resolve()
sys.path.insert(0, str(root_dir))
from gamification import report_single_chapter_result
rel_pfad = f"{Path(__file__).parent.parent.name}/{Path(__file__).parent.name}"
report_single_chapter_result(rel_pfad, res.result.wasSuccessful(), res.result.testsRun)
except Exception:
pass
sys.exit(0 if res.result.wasSuccessful() else 1)

View file

@ -45,6 +45,14 @@ KAPITEL = [
("Kapitel 14: Desktop-GUIs mit Tkinter", "lehrpfad_3_fortgeschrittenes_oop/14_gui_mit_tkinter"),
("Kapitel 15: Parameter & Eigene Container", "lehrpfad_3_fortgeschrittenes_oop/15_parameter_und_container"),
("Kapitel 16: Master-Abschlussprojekt", "lehrpfad_3_fortgeschrittenes_oop/16_master_abschlussprojekt"),
# LEHRPFAD 4: PROFESSIONAL DATA ENGINEERING
("Kapitel 17: Reguläre Ausdrücke (re)", "lehrpfad_4_professional_data_engineering/17_regulaere_ausdruecke_re"),
("Kapitel 18: Funktionale Programmierung", "lehrpfad_4_professional_data_engineering/18_funktionale_programmierung_lambda"),
("Kapitel 19: Generatoren & itertools", "lehrpfad_4_professional_data_engineering/19_generatoren_und_itertools"),
("Kapitel 20: Dataclasses & Type Hints", "lehrpfad_4_professional_data_engineering/20_dataclasses_und_typehints"),
("Kapitel 21: Datenbanken & SQLite", "lehrpfad_4_professional_data_engineering/21_datenbanken_und_sqlite"),
("Kapitel 22: Web-APIs & JSON-Feeds", "lehrpfad_4_professional_data_engineering/22_web_apis_und_json_feeds"),
]
def main():

View file

@ -361,7 +361,13 @@
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "13_persistenz_json_und_csv", title: "Adv 13. Persistenz" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "14_gui_mit_tkinter", title: "Adv 14. Tkinter" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "15_parameter_und_container", title: "Adv 15. Container" },
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "16_master_abschlussprojekt", title: "Master 16. Abschlussprojekt" }
{ track: "lehrpfad_3_fortgeschrittenes_oop", folder: "16_master_abschlussprojekt", title: "Master 16. Abschlussprojekt" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "17_regulaere_ausdruecke_re", title: "Pro 17. Regex & Text Mining" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "18_funktionale_programmierung_lambda", title: "Pro 18. Functional & Lambda" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "19_generatoren_und_itertools", title: "Pro 19. Generatoren & Itertools" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "20_dataclasses_und_typehints", title: "Pro 20. Dataclasses & Decorators" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "21_datenbanken_und_sqlite", title: "Pro 21. SQLite & Relationale DBs" },
{ track: "lehrpfad_4_professional_data_engineering", folder: "22_web_apis_und_json_feeds", title: "Pro 22. REST APIs & JSON Feeds" }
];
function switchTab(tabId) {