Lehrpfad 4 • Professional Data Engineering

Dataclasses, Type Hints & Decorators 🏷️✨

Schreibe eleganten, selbstdokumentierenden und typsicheren Code mit @dataclass, modernen Type Hints und maßgeschneiderten Decorators.

📂 Dateien dieser Einheit:
💻 In Web-IDE öffnen → 📄 aufgabe.py (Startercode) 🧪 test_aufgabe.py (Tests) 💡 musterloesung.py (Lösung) 📖 README.md

1. @dataclass: Die Fertighaus-Gussform 🏗️📦

In vielen Programmen dienen Klassen primär als Datenbehälter. Bisher musstest du dafür seitenweise denselben Standard-Code schreiben:

🔴 Klassisch (Viel Tipparbeit)

class Artikel:
    def __init__(self, name, preis):
        self.name = name
        self.preis = preis

    def __repr__(self):
        return f"Artikel({self.name}, {self.preis})"

    def __eq__(self, other):
        return (self.name, self.preis) == (other.name, other.preis)

🟢 Modern mit @dataclass

from dataclasses import dataclass, field

@dataclass
class Artikel:
    name: str
    preis: float
    kategorie: str = "Allgemein"
✨ __init__, __repr__ und __eq__ sind automatisch fertig!
⚠️ Wichtig: Veränderliche Default-Listen mit field(default_factory=list)

Schreibe in einer Dataclass niemals liste: list = [], da sonst alle Instanzen dieselbe Liste teilen! Nutze stattdessen immer field(default_factory=list).

2. Moderne Type Hints im Überblick 🏷️

Type Hints machen deinen Code selbsterklärend und ermöglichen IDEs wie VS Code blitzschnelle Autovervollständigung und Fehlerwarnungen:

Type Hint Bedeutung Praxis-Beispiel
Optional[str] Kann str oder None sein def suche(id: int) -> Optional[User]: ...
Union[int, float] Zahl kann Ganzzahl oder Fließkomma sein def runden(wert: Union[int, float]) -> float: ...
Callable[[A, B], C] Eine übergebene Funktion def ausfuehren(func: Callable) -> None: ...
Dict[str, Any] Schlüssel sind Strings, Werte beliebig user_config: Dict[str, Any] = {"theme": "dark"}

3. Eigene Decorators schreiben: Das Geschenkverpackungs-Muster 🎁

Ein Decorator ist eine Funktion, die eine andere Funktion einpackt und ihr zusätzliche Fähigkeiten verleiht (z.B. Zeitmessung, Logging, Aufrufzählung):

Eigener Zeitmessungs-Decorator mit @functools.wraps
import time
import functools

def zeitmessung(func):
    @functools.wraps(func)  # Erhält Name und Docstring der Originalfunktion
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        ergebnis = func(*args, **kwargs)
        dauer = time.perf_counter() - start
        print(f"[⏱️] {func.__name__} brauchte {dauer:.6f}s")
        return ergebnis
    return wrapper

@zeitmessung
def schwere_berechnung():
    return sum(x**2 for x in range(500_000))

4. 🎯 Deine Aufgaben in aufgabe.py

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

💡 Lösungshinweise (Aufklappen bei Bedarf)

🔍 Tipp zu TODO 8: Aufruf-Zähler als Decorator

Initialisiere das Attribut am Wrapper vor der Rückgabe:

def aufruf_zaehler(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        wrapper.aufrufe += 1
        return func(*args, **kwargs)
    wrapper.aufrufe = 0
    return wrapper
🔍 Tipp zu TODO 3: @classmethod als Fabrikmethode

cls ist die Klasse selbst. Teile den String und rufe cls(...) auf:

@classmethod
def aus_csv_zeile(cls, csv_zeile: str) -> "Artikel":
    teile = [t.strip() for t in csv_zeile.split(";")]
    return cls(name=teile[0], preis=float(teile[1]), kategorie=teile[2] if len(teile) > 2 else "Allgemein")

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

1. Automatische Tests ausführen:

Terminal (im Ordner 20_dataclasses_und_typehints)
python3 test_aufgabe.py

2. Interaktives Skript starten:

Terminal
python3 aufgabe.py