Type Hints lesen und schreiben
Track KI · M0 Python produktiv, Baustein 03 · ca. 40 Min.
Worum es geht
Alles, was du später mit LLMs baust, hängt an einer Grenze: Auf der einen Seite stehen Daten, denen du nicht trauen darfst, auf der anderen Seite Code, der sich auf Formen und Typen verlassen will. Type Hints beschreiben, was dein Code erwartet. In dieser Lektion lernst du, Type Hints zu lesen und zu schreiben: für Funktionen, Collections, Optionales und strukturelle Typen (Protocol). Die Prüfung der Daten zur Laufzeit (Pydantic) folgt in Teil 2: Pydantic-Modelle und Validatoren.
Du kennst das schon: TypeScript-Typen. Der Unterschied, der Fehler verursacht, wenn du ihn nicht kennst: Der Python-Interpreter ignoriert Type Hints zur Laufzeit. Es prüft sie nur ein eigenes Werkzeug (Typchecker).
Was im Browser läuft: Der Typchecker (pyright, mypy) läuft nicht im Browser. Darum lernst du hier, Type Hints zu lesen und zu schreiben, und das Prüfen erklärt der Text.
Von JS/TS her gedacht
In TypeScript sind Typen und Validierung zwei getrennte Dinge: tsc prüft beim Bauen, zod prüft zur Laufzeit. In Python ist es genauso, nur dass die Typen als normale Syntax im Code stehen.
| Idee | TypeScript | Python |
|---|---|---|
| Typ an Funktion | function add(a: number, b: number): number |
def add(a: int, b: int) -> int: |
| Prüfung der Typen | tsc beim Bauen |
pyright oder mypy (Typchecker), nicht der Interpreter |
| Optional | string \| null |
str \| None |
| Tupel | [string, number] |
tuple[str, float] |
| Struktureller Typ | interface Zahlbar { betrag(): number } |
class Zahlbar(Protocol): def betrag(self) -> float: ... |
Konzept
Schritt 1: Type Hints sind Dokumentation, kein Zaun
Ein Type Hint ist eine Anmerkung (annotation) an Parameter und Rückgabewert. Der Interpreter speichert sie und ignoriert sie sonst:
add("a", "b") läuft durch und gibt "ab" zurück, obwohl -> int dasteht. Ein Typchecker wie pyright oder mypy würde diese Zeile vor dem Ausführen melden, genau wie tsc ein add("a", "b") ablehnt. Der Interpreter tut das nie. Das ist die Stolperfalle der Quelle: Type Hints werden zur Laufzeit standardmäßig nicht erzwungen.
Lesen kannst du die Hints trotzdem, mit typing.get_type_hints. So arbeiten auch Pydantic und FastAPI: Sie lesen die Hints aus und bauen daraus Prüfungen.
Die Schreibweisen von einfach zu komplex (Quelle):
| Hint | Bedeutung | TS-Äquivalent |
|---|---|---|
int, str, float, bool |
Basistypen | number, string, boolean |
list[str], dict[str, float] |
Collections mit Elementtyp | string[], Record<string, number> |
tuple[str, float] |
feste Länge, Typ pro Stelle | [string, number] |
User \| None |
optional (kann None sein) |
User \| null |
list[T] mit T = TypeVar("T") |
generisch (generic) | Array<T> |
Beachte: tuple[str, float] schreibst du nicht als [str, float]. Eckige Klammern allein sind in Python eine Liste, kein Typ.
Protocol beschreibt strukturelle Typisierung (structural typing): Eine Klasse muss kein Interface erben, sie muss nur die passenden Methoden haben. Das ist dasselbe Prinzip wie ein TS-interface.
Ein Typchecker akzeptiert Warenkorb hier, weil die Methode betrag passt. Zur Laufzeit wird auch das nicht geprüft: Der Aufruf scheitert erst, wenn betrag wirklich fehlt.
Falle
Falle 1: Hint heißt nicht Prüfung. def verarbeite(daten: dict) -> int schützt dich nicht davor, dass ein Webservice dir "abc" schickt. Ein Typchecker sieht nur deinen Code, nicht die Daten, die zur Laufzeit hereinkommen. An der Grenze zur Außenwelt braucht es zusätzlich eine Laufzeitprüfung (Pydantic, Teil 2, Quelle).
Falle 2: TS-Schreibweise für Tupel. [str, float] ist in Python kein Typ, sondern eine Liste mit zwei Klassen darin. Richtig ist tuple[str, float]. Übung 2 lässt dich genau das reparieren.
Falle 3: Protocol wird zur Laufzeit nicht erzwungen. Der Typchecker meldet eine Klasse, der die Methode fehlt. Der Interpreter merkt es erst, wenn der Code die Methode wirklich aufruft (AttributeError). Das siehst du in Übung 3.
Übungen
Übung 1: Code vorhersagen (leicht)
Diese Funktion hat Type Hints, wird aber teils falsch benutzt:
from typing import get_type_hints
def verdopple(wert: int) -> int:
return wert + wertTrage ein Tupel mit vier Werten ein: (1) das Ergebnis von verdopple(21), (2) das Ergebnis von verdopple("ab"), (3) das Ergebnis von verdopple(None) und (4) list(get_type_hints(verdopple)). Wirft ein Aufruf einen Fehler, trage den Namen der Exception als String ein, zum Beispiel "KeyError". Rechne erst im Kopf. Zur Erinnerung aus Schritt 1: Der Interpreter prüft keine Hints, und + zwischen zwei Strings hängt sie aneinander.
Wichtig ist, welche Operation der Funktionskörper mit dem Wert ausführt und ob sie für diesen Typ definiert ist. Bei Teil 4: Welche Namen stehen in den Annotationen?
antwort = (42, "abab", "TypeError", ["wert", "return"])
antwortÜbung 2: Type Hints ergänzen (mittel)
Das ist Baustein 03 der Quelle, verkleinert für den Browser: Ein untypisiertes Skript soll vollständig Hints bekommen, inklusive Rückgabetyp. Ein Messwert ist ein Tupel (zaehler_id, wert) aus einem str und einem float. Ergänze die Hints für alle drei Funktionen. Die Logik darfst du nicht ändern.
Hinweis zur Prüfung: Dein Code muss mit get_type_hints lesbar sein, und die Hints müssen zu dem passen, was die Funktionen wirklich tun. Das ist das, was ein Typchecker auch prüfen würde.
Gehe Funktion für Funktion vor. Was geht hinein, was kommt heraus? Bei Collections gehört der Elementtyp dazu: list allein sagt nicht, was in der Liste steckt. Und ein Tupel hat pro Stelle einen eigenen Typ.
def parse_messwert(zeile: str) -> tuple[str, float]:
zaehler, wert = zeile.split(";")
return zaehler, float(wert)
def filtere_ab(werte: list[tuple[str, float]], grenze: float) -> list[tuple[str, float]]:
return [(z, w) for z, w in werte if w >= grenze]
def zaehle_pro_zaehler(werte: list[tuple[str, float]]) -> dict[str, int]:
anzahl = {}
for z, _ in werte:
anzahl[z] = anzahl.get(z, 0) + 1
return anzahl
(parse_messwert, filtere_ab, zaehle_pro_zaehler)Übung 3: Protocol und Typchecker (mittel, ca. 8 Min.)
Ein Typchecker prüft vor dem Ausführen, ob ein Objekt zum Protocol passt (hat es die Methode mit passender Signatur?). Zur Laufzeit passiert dagegen nur, was der Code tut. Hier ein neues Beispiel:
from typing import Protocol
class Druckbar(Protocol):
def als_text(self) -> str: ...
class Rechnung:
def als_text(self) -> str:
return "Rechnung 7"
class Angebot:
def zu_text(self) -> str:
return "Angebot 3"
def drucke(objekt: Druckbar) -> str:
return objekt.als_text().upper()Trage ein Tupel mit vier Werten ein: (1) das Ergebnis von drucke(Rechnung()), (2) was drucke(Angebot()) zur Laufzeit tut (Name der Exception als String, falls sie einen Fehler wirft), (3) ob ein Typchecker den Aufruf drucke(Angebot()) melden würde (True oder False) und (4) ob er drucke(Rechnung()) melden würde.
Trenne zwei Fragen: Was passiert, wenn der Code läuft und ein Name fehlt? Und was prüft ein Typchecker, ohne den Code auszuführen? Bei Protocol zählt, welche Methoden die Klasse hat, nicht von wem sie erbt.
antwort = ("RECHNUNG 7", "AttributeError", True, False)
antwortRechnung erbt nichts von Druckbar, hat aber als_text, also passt sie strukturell. Angebot hat nur zu_text: Der Typchecker meldet den Aufruf, und zur Laufzeit fehlt als_text (AttributeError).
Merksatz
Type Hints sind Dokumentation für Menschen und Typchecker, kein Zaun zur Laufzeit: Der Interpreter ignoriert sie, und nur ein Typchecker prüft sie vor dem Ausführen.
Prüfstein
Eine Kollegin sagt: “Ich habe überall Type Hints, also kann in meine Funktionen kein falscher Typ mehr hineinkommen.” Was antwortest du? Nenne, was Hints leisten, was sie nicht leisten und wo du die Lücke schließt.
Weiter: Teil 2: Pydantic-Modelle und Validatoren schließt die Lücke an der Grenze zur Außenwelt.
Quelle: quellen/kursbuch-lerninhalte.md, Modul M0, Baustein “03 Type Hints” (Zeilen 138 bis 190 der Datei, zusammen mit Baustein 04). Über die Quelle hinaus (allgemeines Fachwissen, mit echtem Python 3.13 geprüft): get_type_hints, Protocol und strukturelle Typisierung im Detail, Vergleichstabelle mit TypeScript. Typchecker (pyright, mypy) wurden nicht ausgeführt, in Übung 3 ersetzt ein Namensvergleich die Typprüfung (stark vereinfacht).