Zum Inhalt springen
aviral gupta

// A2.1 · ca. 30 Min. · Vertiefung

Typ-Hinweise und ein Typprüfer

Nach dieser Lektion annotieren Sie Funktionen und Variablen mit list[int], dict[str, float] und X | None, führen mypy aus, lesen seine Meldungen und erklären, warum Python selbst keinen Hinweis prüft.

Lektion 1 von 6 in A2 Typisierung

Anfang des Moduls

Danach können Sie

  • Parameter, Rückgabewerte und Variablen mit eingebauten Generics und X | None annotieren
  • mypy auf eine Datei anwenden und seine Meldungen und Fehlercodes lesen
  • Vorhersagen, was Hinweise zur Laufzeit tun: Sie werden gespeichert, nie geprüft
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus der Mittelstufe: Was ist -> float in def area(width: float, height: float) -> float:?

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: n ist als int annotiert. Was gibt das aus?

    def double(n: int) -> int:
        return n * 2
    
    
    print(double("ab"))
  3. Üben · Aufgabe 3 von 7

    first() liefert das erste Element oder None bei einer leeren Liste. Setzen Sie den Operator ein, damit der Rückgabe-Hinweis „ein int oder None“ sagt.

    def first(items: list[int]) -> int ____ None:
        return items[0] if items else None
    int None:
  4. Üben · Aufgabe 4 von 7

    mypy meldet für Zeile 4 dieser Datei zwei Fehler. Welchen Fehlercode setzt es bei beiden in Klammern ans Ende?

    def total(prices: list[float]) -> float:
        return sum(prices)
    
    
    print(total(["3.50", "1.20"]))
  5. Üben · Aufgabe 5 von 7

    Ordnen Sie jeder Annotation einen Wert zu, den sie beschreibt.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Sie führen python -m mypy main.py ohne Optionen auf dieser Datei aus. Was ist die letzte Zeile, die mypy ausgibt?

    def load():
        count: int = "three"
        return count
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe, auf Ihrem eigenen Python mit installiertem mypy. Schreiben Sie parse_price(text), das die Zahl in text als float liefert oder None, wenn text keine Zahl ist, und total(prices), das eine Liste solcher Ergebnisse addiert und None überspringt. Annotieren Sie beide und führen Sie python -m mypy --strict aus, bis es keine Probleme mehr meldet.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Hinweise gespeichert, nicht geprüft

Zwei annotierte Funktionen und eine annotierte Variable. Das Programm gibt die gespeicherten Hinweise aus und ruft dann mean() mit einem Tupel von ints auf, was sein Hinweis nicht erlaubt. Python führt den Aufruf trotzdem aus. Ohne den Kommentar # type: ignore[arg-type] meldet mypy diese Zeile: error: Argument 1 to "mean" has incompatible type "tuple[int, int, int]"; expected "list[float]" [arg-type].

main.py

# Hints describe types; Python stores them but never checks them.
def mean(values: list[float]) -> float:
    return sum(values) / len(values)


def find(stock: dict[str, int], name: str) -> int | None:
    return stock.get(name)


stock: dict[str, int] = {"apples": 4, "pears": 0}

print(mean.__annotations__)
print(find.__annotations__["return"])
print(mean([1.5, 2.5]))
# A tuple of ints: mypy objects, and this comment silences that error.
print(mean((1, 2, 3)))  # type: ignore[arg-type]
print(find(stock, "plums"))

Ausführen mit

python main.py

Ausgabe

{'values': list[float], 'return': <class 'float'>}
int | None
2.0
2.0
None
  • __annotations__ enthält die Hinweise als Objekte: list[float] ist ein Generic Alias, float die Klasse selbst.
  • int | None ist ein Union-Objekt; es wird so ausgegeben, wie Sie es geschrieben haben.
  • mean((1, 2, 3)) funktioniert, weil sum() und len() ein Tupel akzeptieren. Nur mypy widerspricht, und der Kommentar schaltet genau diesen Fehlercode stumm.
  • find() liefert None für einen fehlenden Namen, genau das verspricht das | None im Hinweis.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Die Preisfunktionen annotieren

Die drei Funktionen funktionieren, haben aber keine Hinweise. Annotieren Sie sie: parse_price nimmt einen str und liefert ein float oder None; cheapest nimmt eine Liste von floats und liefert ein float oder None; label nimmt einen Namen (str) und einen Preis, der None sein kann, und liefert einen str. Die Tests lesen die Hinweise mit typing.get_type_hints, schreiben Sie sie also genau so.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Das Muster ist def name(param: Type) -> ReturnType:. Ein Ergebnis, das fehlen kann, ist float | None.

  2. Hinweis 2

    Eine Liste von floats ist list[float]; weder list noch der Operator | brauchen einen Import.

  3. Hinweis 3

    label(name: str, price: float | None) -> str: Der Parameter price kann None sein, also sagt sein Hinweis das.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

def parse_price(text: str) -> float | None:
    try:
        return float(text)
    except ValueError:
        return None


def cheapest(prices: list[float]) -> float | None:
    if not prices:
        return None
    return min(prices)


def label(name: str, price: float | None) -> str:
    if price is None:
        return f"{name}: no price"
    return f"{name}: {price:.2f} EUR"
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

def parse_price(text):
    try:
        return float(text)
    except ValueError:
        return None


def cheapest(prices):
    if not prices:
        return None
    return min(prices)


def label(name, price):
    if price is None:
        return f"{name}: no price"
    return f"{name}: {price:.2f} EUR"

test_main.py

from typing import get_type_hints

from main import cheapest, label, parse_price


def test_parse_price_hints():
    """parse_price nimmt einen str und liefert float | None"""
    got = get_type_hints(parse_price)
    assert got == {"text": str, "return": float | None}, f"die Hinweise von parse_price sind {got!r}"


def test_cheapest_hints():
    """cheapest nimmt list[float] und liefert float | None"""
    got = get_type_hints(cheapest)
    assert got == {"prices": list[float], "return": float | None}, f"die Hinweise von cheapest sind {got!r}"


def test_label_hints():
    """label nimmt einen str und ein float | None und liefert einen str"""
    got = get_type_hints(label)
    assert got == {"name": str, "price": float | None, "return": str}, f"die Hinweise von label sind {got!r}"


def test_behaviour():
    """Die Funktionen arbeiten weiter: 2.5, None, 1.25 und die beiden Labels"""
    got = parse_price("2.5"), parse_price("free"), cheapest([2.5, 1.25]), cheapest([])
    assert got == (2.5, None, 1.25, None), f"die Funktionen lieferten {got!r}"
    got_labels = label("tea", 2.5), label("cake", None)
    assert got_labels == ("tea: 2.50 EUR", "cake: no price"), f"label lieferte {got_labels!r}"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 2

mypy sauber bekommen

Diese Übung läuft auf Ihrem eigenen Python: Installieren Sie mypy (python -m pip install mypy) und führen Sie python -m mypy --check-untyped-defs main.py aus. Es meldet zwei Fehler, und der erste ist ein echter Bug: read_scores behält die Punkte als Strings. Beheben Sie beide, bis mypy keine Probleme meldet und die Tests bestehen. Ändern Sie den Code, wo der Hinweis stimmt, und den Hinweis, wo der Code stimmt.

Diese Übung braucht Python auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    mypy leitet scores = {} aus dem ersten gespeicherten Wert ab, sieht also dict[str, str] und meldet, dass der Rückgabewert nicht passt.

  2. Hinweis 2

    Der Hinweis dict[str, int] stimmt; wandeln Sie die Punkte mit int(points) um.

  3. Hinweis 3

    Division mit / ergibt immer ein float, also ist beim Rückgabe-Hinweis von average der Hinweis zu ändern: -> float.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

def read_scores(lines: list[str]) -> dict[str, int]:
    scores: dict[str, int] = {}
    for line in lines:
        name, points = line.split(",")
        scores[name] = int(points)
    return scores


def average(scores: dict[str, int]) -> float:
    return sum(scores.values()) / len(scores)


def best(scores: dict[str, int]) -> str | None:
    if not scores:
        return None
    return max(scores, key=lambda name: scores[name])
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

def read_scores(lines: list[str]) -> dict[str, int]:
    scores = {}
    for line in lines:
        name, points = line.split(",")
        scores[name] = points
    return scores


def average(scores: dict[str, int]) -> int:
    return sum(scores.values()) / len(scores)


def best(scores: dict[str, int]) -> str | None:
    if not scores:
        return None
    return max(scores, key=lambda name: scores[name])

test_main.py

from main import average, best, read_scores


def test_mypy_is_clean():
    """mypy --check-untyped-defs meldet keine Fehler für main.py"""
    from mypy import api

    report, errors, status = api.run(["--check-untyped-defs", "--no-error-summary", "main.py"])
    assert status == 0, f"mypy hat Probleme gefunden:\n{report}{errors}"


def test_read_scores():
    """read_scores macht aus "ada,3" den Eintrag {"ada": 3}"""
    got = read_scores(["ada,3", "bob,5"])
    assert got == {"ada": 3, "bob": 5}, f"read_scores lieferte {got!r}, erwartet waren ints als Werte"


def test_average():
    """Der Durchschnitt von 3 und 5 ist 4.0"""
    got = average({"ada": 3, "bob": 5})
    assert got == 4.0, f"average lieferte {got!r}, erwartet war 4.0"


def test_best():
    """best nennt die höchste Punktzahl und liefert None ohne Punkte"""
    got = best({"ada": 3, "bob": 5}), best({})
    assert got == ("bob", None), f"best lieferte {got!r}, erwartet war ('bob', None)"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Erwarten, dass der Hinweis das Argument prüft

def mean(values: list[float]) -> float:
    return sum(values) / len(values)


print(mean(["1.5", "2.5"]))

Was Python ausgibt

TypeError: unsupported operand type(s) for +: 'int' and 'str'

Warum, und die Lösung

Der Hinweis list[float] hat die Strings nicht aufgehalten: Python hat den Aufruf ausgeführt, und sum() scheiterte in der Funktion, weit weg vom eigentlichen Fehler. Hinweise prüft nur ein Typprüfer. python -m mypy main.py meldet List item 0 has incompatible type "str"; expected "float" in der Aufrufzeile. Wandeln Sie Eingaben dort um, wo sie ins Programm kommen.

Ein X | None-Ergebnis wie ein X verwenden

def find(names: dict[str, str], key: str) -> str | None:
    return names.get(key)


print(find({"ada": "Ada Lovelace"}, "bob").upper())

Was Python ausgibt

AttributeError: 'NoneType' object has no attribute 'upper'

Warum, und die Lösung

str | None verspricht, dass das Ergebnis None sein kann, also muss der Aufrufer das klären, bevor er eine str-Methode aufruft. mypy meldet es, ohne etwas auszuführen: Item "None" of "str | None" has no attribute "upper" [union-attr]. Prüfen Sie zuerst, if name is not None:, oder geben Sie mit names.get(key, "") einen Standardwert.

Glauben, ein Variablen-Hinweis wandle den Wert um

count: int = "3"
print(count + 1)

Was Python ausgibt

TypeError: can only concatenate str (not "int") to str

Warum, und die Lösung

Eine Annotation an einer Variablen wandelt nichts um: count enthält weiter den String "3". Wandeln Sie ausdrücklich um: count: int = int("3"). mypy meldet die ursprüngliche Zeile: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment].

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Hinweise beschreiben die gemeinten Typen

Ein Hinweis steht nach einem Doppelpunkt am Parameter oder an der Variablen und nach einem Pfeil am Rückgabewert: def mean(values: list[float]) -> float:. Eingebaute Sammlungen nehmen ihre Elementtypen in eckigen Klammern: list[int], dict[str, float], set[str], tuple[str, int] für genau zwei Elemente und tuple[int, ...] für beliebig viele. X | Y heißt „einer von beiden“, und X | None ist die übliche Art, „kann fehlen“ zu sagen. typing.Optional[X] und Union[X, Y] bedeuten dasselbe und sind zur Laufzeit gleich, aber | braucht keinen Import.

mypy liest den Code, Python führt ihn aus

mypy ist ein eigenes Programm: Installieren mit python -m pip install mypy, aufrufen mit python -m mypy main.py. Es führt Ihren Code nie aus, sondern liest ihn und meldet Zeilen wie main.py:4: error: Argument 1 to "add" has incompatible type "float"; expected "int" [arg-type]: Datei, Zeile, Meldung und ein Fehlercode in Klammern. Eine saubere Datei endet mit Success: no issues found. Standardmäßig überspringt mypy den Rumpf einer Funktion ganz ohne Annotationen; --check-untyped-defs, das dieser Kurs verwendet, prüft auch diese.

Zur Laufzeit sind Hinweise nur Daten

Der Interpreter speichert Hinweise im __annotations__ der Funktion und sieht sie beim Aufruf nie an. double("ab") mit n: int läuft und liefert "abab"; count: int = "3" lässt einen String in count. Ein falscher Typ fällt erst auf, wenn eine Operation scheitert, oder gar nicht. Das ist die Arbeitsteilung: mypy, Ihr Editor und Ihre Leser nutzen die Hinweise, bevor das Programm läuft; Python ignoriert sie, während es läuft.

Quellen

Zuletzt geprüft am 29. September 2026