Python-Typannotationen (Type Hints) ändern die Laufzeit nicht – sie sind eine Beschreibung für Menschen und Werkzeuge. Der Nutzen entsteht durch die Prüfung im Editor und in der Pipeline: Fehler werden gefunden, bevor das Programm läuft.
\nGrundlagen
\n# Variablen und Funktionen\nname: str = "web01"\nzaehler: int = 0\naktiv: bool = False\nwerte: list[int] = []\nzuordnung: dict[str, int] = {}\n\ndef begruesse(name: str, laut: bool = False) -> str:\n return f"Hallo {name}" if not laut else f"HALLO {name.upper()}!"\n\ndef summiere(zahlen: list[float]) -> float:\n return sum(zahlen)\n\ndef hole_server(hostname: str) -> dict[str, str] | None:\n "Gibt None zurueck, wenn der Server nicht existiert."\n ...\n\n# Typen koennen verschiedene Formen annehmen\nfrom typing import Optional, Union, Any\n\n# Beides gleichbedeutend (ab Python 3.10 ist die Kurzform ueblicher):\ndef a(x: Optional[str]) -> None: ... # alt\ndef b(x: str | None) -> None: ... # ab 3.10, klarer\n\ndef c(x: Union[int, str]) -> None: ... # alt\ndef d(x: int | str) -> None: ... # ab 3.10\n\n# Ab Python 3.9 sind die eingebauten Typen direkt verwendbar:\n# list[int] statt List[int] (aus typing)\n# dict[str, int] statt Dict[str, int]\n# tuple[int, str] statt Tuple[int, str]\n# set[str] statt Set[str]\n# from __future__ import annotations # macht Annotationen ueberall zu Strings\n# -> noetig, wenn man sehr neue Syntax in aelteren Versionen nutzen will\nWerkzeuge: die Prüfung
\n# 1. Statische Pruefung - ohne Ausfuehrung des Programms\npip install mypy\nmypy app.py\nmypy --strict app.py # alle strikten Pruefungen\nmypy app.py --ignore-missing-imports # ohne Stubs fuer Bibliotheken\n\n# 2. Schnellerer Pruefer, gut fuer die Pipeline\npip install pyright\npyright app.py\n\n# 3. Editiereinbindung - der groesste Nutzen im Alltag\n# VS Code: Pylance (bringt pyright mit)\n# Neovim: pyright oder mypy per LSP\n# -> Fehler erscheinen, WAEHREND man schreibt, nicht erst im Test\n\n# Beispielhafter Fehler, den mypy findet:\ndef verarbeite(zahlen: list[int]) -> int:\n return sum(zahlen)\n\nverarbeite([1, 2, 3]) # ok\nverarbeite(["a", "b"]) # Fehler: list[str] ist nicht list[int]\nverarbeite("123") # Fehler: str ist nicht list[int]\n# Alle drei Varianten laufen in Python OHNE mypy bis zum Absturz\n# oder - schlimmer - bis zu einem falschen Ergebnis durch.\nFortgeschrittene Typen
\nfrom collections.abc import Iterable, Callable, Iterator, Sequence\nfrom typing import Protocol, TypedDict, Literal, Annotated, TypeVar\n\n# Eingabe: "ich akzeptiere alles Aufzaehlbare" - nicht nur eine Liste\ndef mittel(werte: Iterable[float]) -> float:\n werte = list(werte) # einmal in eine Liste wandeln\n return sum(werte) / len(werte)\n\nmittel([1.0, 2.0])\nmittel({1.0, 2.0}) # Menge - funktioniert auch\nmittel(x for x in range(5)) # Generator - kein Speicherverbrauch\n\n# Ausgabe: "ich liefere einen Iterator" - wichtig fuer grosse Daten\ndef lies_zeilen(pfad: str) -> Iterator[str]:\n with open(pfad, encoding="utf-8") as f:\n for zeile in f:\n yield zeile.rstrip("\\n")\n\n# Funktionen als Parameter\ndef anwenden(werte: list[int], f: Callable[[int], int]) -> list[int]:\n return [f(w) for w in werte]\n\nanwenden([1, 2, 3], lambda x: x * 2)\n\n# Strukturierte Wörterbücher statt dict[str, object]\nclass Server(TypedDict):\n name: str\n ip: str\n port: int\n\ndef verbinde(server: Server) -> None:\n print(server["name"], server["ip"], server["port"])\n# Tippfehler in den Schluesseln "name"/"ip"/"port" werden gemeldet!\n\n# Genau festgelegte Werte\nModus = Literal["lesen", "schreiben", "anhaengen"]\n\ndef oeffne(pfad: str, modus: Modus = "lesen") -> None: ...\noeffne("x.txt", "schreiben") # ok\noeffne("x.txt", "schreiben!") # Fehler - nicht im Literal\n\n# Eigenes Verhalten beschreiben (Protocol statt Vererbung!)\nclass Schliessbar(Protocol):\n def schliessen(self) -> None: ...\n\ndef aufraeumen(objekte: list[Schliessbar]) -> None:\n for o in objekte:\n o.schliessen()\n# Jede Klasse mit passender schliessen()-Methode passt -\n# ohne von Schliessbar zu erben. Sehr praktisch in der Praxis.\n\n# Typvariablen fuer generische Funktionen\nT = TypeVar("T")\n\ndef erstes(werte: Sequence[T]) -> T | None:\n return werte[0] if werte else None\n# mypy leitet ab: erstes([1,2]) -> int | None\n# erstes(["a"]) -> str | None\n\n# Zusaetzliche Angaben, die nicht zur Laufzeit geprueft werden\nfrom typing import Annotated\n\ndef hole(pfad: Annotated[str, "absoluter Pfad ohne '..'"],\n zeitlimit: Annotated[float, "in Sekunden"] = 5.0) -> bytes:\n ...\n# Die Zusatzangaben sind dokumentierend (oder fuer Werkzeuge wie\n# pydantic nutzbar) und veraendern das Verhalten nicht.\nWas Typannotationen nicht leisten
\n- \n
- Keine Prüfung zur Laufzeit. Python prüft die Annotationen nicht.
verarbeite("123")läuft ohne Fehler durch, wenn man nur die Annotation ändert. Die Prüfung muss ein statisches Werkzeug übernehmen. \n - Keine Typumwandlung.
zahlen: list[int] = input()– die Annotation wandelt nichts. Das ist ein häufiges Missverständnis bei Einsteigern. \n - Keine Garantie bei Daten von außen. Eine JSON-Antwort ist zur Laufzeit unbekannt. Dafür braucht es echte Prüfung (pydantic, attrs, oder eigene Prüfungen). \n
- Keine Leistungsverbesserung. Annotationen machen Programme nicht schneller. (Ausnahme: Werkzeuge wie Numba oder mypyc können sie nutzen.) \n
Prüfung zur Laufzeit, wo sie nötig ist
\n# Pydantic ist der Standard fuer Daten von aussen (APIs, Dateien, Formulare)\nfrom pydantic import BaseModel, Field, field_validator\n\nclass Server(BaseModel):\n name: str = Field(min_length=1, max_length=64)\n ip: str\n port: int = Field(ge=1, le=65535)\n\n @field_validator("ip")\n @classmethod\n def ip_muss_gueltig_sein(cls, v: str) -> str:\n import ipaddress\n try:\n ipaddress.ip_address(v) # jetzt wird wirklich geprueft!\n except ValueError as fehler:\n raise ValueError(f"ungueltige IP: {v}") from fehler\n return v\n\n# Aus JSON erzeugen - mit Pruefung und klarer Fehlermeldung\nroh = '{"name": "web01", "ip": "10.0.0.11", "port": 8080}'\nserver = Server.model_validate_json(roh) # wirft bei Fehlern\nprint(server.name, server.ip, server.port)\nprint(server.model_dump_json())\n\n# Fuer Daten aus dem eigenen Programm genuegt mypy.\n# Fuer Daten von aussen ist eine echte Pruefung Pflicht.\nUmgang in bestehenden Projekten
\n- \n
- Nicht alles auf einmal.
mypyin einem gewachsenen Projekt erzeugt hunderte Meldungen. Sinnvoll ist ein stufenweises Vorgehen. \n - Loslassen, was nicht passt: Einzelne Module oder Zeilen übergehen:\n
# mypy.ini\n[mypy]\npython_version = 3.11\nwarn_unused_ignores = True\ndisallow_untyped_defs = False # erst spaeter einschalten\n\n[mypy-altes_modul.*]\nignore_errors = True\n# Neue Module sofort streng pruefen, alte bei Gelegenheit nachziehen.\n - Fehler einzeln bewerten, nicht pauschal ignorieren.
# type: ignoreohne Kommentar ist ein versteckter Fehler. Mit Grund ist es vertretbar:\nwert = daten["gemischt"] # type: ignore[index] # API liefert leider gemischte Typen\n# Mit "warn_unused_ignores = True" meldet mypy, wenn eine\n# Uebergehung nicht mehr noetig ist - dann entfernen.\n - In die Pipeline aufnehmen. Ohne Durchsetzung veralten Annotationen und werden irreführend. Ein Prüflauf bei jedem Änderungssatz hält den Bestand sauber. \n
- Klare Namen verwenden, keine
Any-Flucht. Wer überallAnyschreibt, hat den Nutzen aufgegeben.objectoderProtocolsind meist die bessere Wahl. \n
Kurz: was sofort Nutzen bringt
\n# Selbst ohne meine strenge Pruefung sind diese Annotationen sofort wertvoll:\ndef verarbeite(pfad: str, zeitlimit: float = 5.0) -> list[dict[str, str]]:\n "Liest Konfigurationseintraege aus einer Datei."\n ...\n\n# Sie zeigen im Editor:\n# - welche Parameter eine Funktion erwartet und welche Typen sie hat\n# - was sie zurueckgibt (auch: dass sie NICHTS zurueckgibt)\n# - ob ein Attribut existiert, das man gerade schreiben will\n# - unbekannte oder falsch geschriebene Parameter beim Aufruf\n\n# Der groesste praktische Vorteil ist nicht die Pruefung,\n# sondern die Vervollstaendigung und das Nachschlagen im Editor.\n# Deshalb lohnt es sich auch in kleineren Skripten.\nVerwandte Grundlagen: Python: Variablen und Datentypen, pytest, Python-Befehle, Code-Review.