Zum Inhalt springen
aviral gupta

// A2.6 · ca. 45 Min. · Vertiefung

Annotationen zur Laufzeit in 3.14

Nach dieser Lektion wissen Sie, wann Python 3.14 Annotationen auswertet, lesen sie sicher mit annotationlib und haben ein ganzes Modul Schritt für Schritt typisiert, bis mypy --strict keine Probleme meldet.

Lektion 6 von 6 in A2 Typisierung

Ende des Moduls

Danach können Sie

  • Die verzögerte Auswertung in 3.14 erklären: wann Annotationen laufen und was from __future__ import annotations ändert
  • Annotationen mit annotationlib.get_annotations in den Formaten VALUE, FORWARDREF und STRING lesen
  • Ein untypisiertes Modul Schritt für Schritt typisieren, bis mypy --strict keine Probleme meldet
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus Lektion A2.1: Der Hint sagt int, aber was wird ausgegeben?

    def half(x: int) -> int:
        return x / 2
    
    
    print(half(3))
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Cls steht in einer Annotation, bevor es definiert ist. Was gibt Python 3.14 aus?

    def func(a: Cls) -> None:
        print(a)
    
    
    class Cls:
        pass
    
    
    print(func.__annotations__)
  3. Üben · Aufgabe 3 von 7

    Missing ist nirgends definiert. Ergänzen Sie das Format, das die Annotationen als Quelltext liefert, ohne Ausnahme.

    get_annotations(f, format=Format.____)
    Format.)
  4. Üben · Aufgabe 4 von 7

    Die Annotation ruft eine Funktion auf, die etwas ausgibt. Was gibt das Programm aus?

    def expensive():
        print("evaluating", end=" ")
        return int
    
    
    def f(x: expensive()) -> None:
        pass
    
    
    print("defined", end=" ")
    f.__annotations__
    f.__annotations__
    print("done")
  5. Üben · Aufgabe 5 von 7

    Eine Annotation nennt Missing, das nicht definiert ist. Ordnen Sie jeder Art, Annotationen zu lesen, das Ergebnis zu.

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Das Modul hat noch den alten __future__-Import. Was wird ausgegeben?

    from __future__ import annotations
    
    from annotationlib import get_annotations
    
    
    def scale(n: int) -> int:
        return n
    
    
    print(get_annotations(scale)["n"] is int, get_annotations(scale, eval_str=True)["n"] is int)
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Invoice hat die Felder number: int, amount: Decimal und paid: bool = False, wobei Decimal nur unter if TYPE_CHECKING: importiert wird. Schreiben Sie describe(cls), das je Feld eine Zeile "name: typ" liefert, und geben Sie sie aus. Es muss funktionieren, obwohl Decimal zur Laufzeit nicht existiert, und mypy --strict bestehen.

    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

Drei Arten, dieselben Annotationen zu lesen

Node verweist ohne Anführungszeichen auf sich selbst; das funktioniert, weil Annotationen verzögert ausgewertet werden. price nutzt Decimal, das nur für den Type Checker importiert wird. Seine Annotationen in den Formaten STRING und FORWARDREF zu lesen funktioniert; das Standardformat VALUE löst NameError aus. Das Programm besteht mypy --strict.

main.py

from annotationlib import Format, get_annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from decimal import Decimal


class Node:
    def __init__(self, value: int, next: Node | None = None) -> None:
        self.value = value
        self.next = next


def price(amount: Decimal) -> str:
    return f"{amount:.2f}"


print(get_annotations(Node.__init__))
print(get_annotations(price, format=Format.STRING))
ref = get_annotations(price, format=Format.FORWARDREF)["amount"]
print(type(ref).__name__, ref.__forward_arg__)
try:
    get_annotations(price)
except NameError as error:
    print("VALUE:", error)

Ausführen mit

python main.py

Ausgabe

{'value': <class 'int'>, 'next': __main__.Node | None, 'return': None}
{'amount': 'Decimal', 'return': 'str'}
ForwardRef Decimal
VALUE: name 'Decimal' is not defined
  • Node | None wird ausgewertet, wenn die Annotationen gelesen werden, also nachdem die Klasse existiert.
  • TYPE_CHECKING ist zur Laufzeit False, Decimal wird also nie importiert; mypy behandelt es als True.
  • Ein ForwardRef bewahrt den Text des Namens in __forward_arg__ und kann später ausgewertet werden.
  • Nur das Format VALUE braucht jeden Namen.
Ä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 4

Projektschritt 1: das Modul annotieren

Dieses Modul liest Artikel aus Zeilen wie "pen; 1.50" und hat keine Hints. Annotieren Sie jeden Parameter und jeden Rückgabewert: name ist ein str, price ein float, tags eine list[str] oder None, und cheapest liefert ein Item oder None. total liefert einen float. Ändern Sie den Code selbst nicht.

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

    Methoden brauchen -> None, wenn sie nichts zurückgeben: def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:

  2. Hinweis 2

    Item ist über den Funktionen definiert, und selbst wenn nicht, würde 3.14 die Hints verzögert auswerten.

  3. Hinweis 3

    cheapest liefert None für eine leere Liste, also ist sein Rückgabe-Hint Item | None.

Eine Lösung zeigen

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

class Item:
    def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:
        self.name = name
        self.price = price
        self.tags = tags or []


def parse_item(line: str) -> Item:
    name, price = line.split(";")
    return Item(name.strip(), float(price))


def load(text: str) -> list[Item]:
    return [parse_item(line) for line in text.splitlines() if line.strip()]


def cheapest(items: list[Item]) -> Item | None:
    if not items:
        return None
    return min(items, key=lambda item: item.price)


def total(items: list[Item]) -> float:
    return sum(item.price for item in items)
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

class Item:
    def __init__(self, name, price, tags=None):
        self.name = name
        self.price = price
        self.tags = tags or []


def parse_item(line):
    name, price = line.split(";")
    return Item(name.strip(), float(price))


def load(text):
    return [parse_item(line) for line in text.splitlines() if line.strip()]


def cheapest(items):
    if not items:
        return None
    return min(items, key=lambda item: item.price)


def total(items):
    return sum(item.price for item in items)

test_main.py

from annotationlib import get_annotations

import main

PARAMETERS = {"parse_item": ["line"], "load": ["text"], "cheapest": ["items"], "total": ["items"]}


def test_every_function_is_annotated():
    """Jeder Parameter und jeder Rückgabewert hat eine Annotation"""
    for name, params in PARAMETERS.items():
        hints = get_annotations(getattr(main, name))
        missing = [p for p in [*params, "return"] if p not in hints]
        assert not missing, f"{name} hat keine Annotation für {missing}"
    hints = get_annotations(main.Item.__init__)
    missing = [p for p in ["name", "price", "tags", "return"] if p not in hints]
    assert not missing, f"Item.__init__ hat keine Annotation für {missing}"


def test_precise_types():
    """cheapest kann None liefern, total liefert einen float, und tags darf None sein"""
    assert get_annotations(main.cheapest)["return"] == (main.Item | None), "cheapest sollte Item | None liefern"
    assert get_annotations(main.total)["return"] is float, "total sollte float liefern"
    assert get_annotations(main.Item.__init__)["tags"] == (list[str] | None), "tags sollte list[str] | None sein"


def test_behaviour():
    """load, cheapest und total funktionieren weiterhin"""
    items = main.load("pen; 1.50\nbook; 12\n")
    got = main.cheapest(items).name, main.total(items), main.cheapest([])
    assert got == ("pen", 13.5, None), f"cheapest und total lieferten {got!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 4

Projektschritt 2: beheben, was mypy findet

Das Modul hat jetzt Hints und eine neue Funktion summary. mypy meldet sie: Item "None" of "Item | None" has no attribute "name" [union-attr]. Der Fehler ist echt: summary([]) stürzt ab. Grenzen Sie das Ergebnis von cheapest ein, sodass eine leere Liste "no items" ergibt, und behalten Sie den Text für eine nicht leere Liste bei.

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

    cheapest liefert Item | None, und None hat kein Attribut name.

  2. Hinweis 2

    Speichern Sie das Ergebnis in best und prüfen Sie dann if best is None: return "no items".

  3. Hinweis 3

    Nach diesem Test grenzt mypy best auf Item ein, und die letzte Zeile besteht die Typprüfung.

Eine Lösung zeigen

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

class Item:
    def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:
        self.name = name
        self.price = price
        self.tags = tags or []


def parse_item(line: str) -> Item:
    name, price = line.split(";")
    return Item(name.strip(), float(price))


def load(text: str) -> list[Item]:
    return [parse_item(line) for line in text.splitlines() if line.strip()]


def cheapest(items: list[Item]) -> Item | None:
    if not items:
        return None
    return min(items, key=lambda item: item.price)


def total(items: list[Item]) -> float:
    return sum(item.price for item in items)


def summary(items: list[Item]) -> str:
    best = cheapest(items)
    if best is None:
        return "no items"
    return f"{len(items)} items, {total(items):.2f} in total, cheapest: {best.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

class Item:
    def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:
        self.name = name
        self.price = price
        self.tags = tags or []


def parse_item(line: str) -> Item:
    name, price = line.split(";")
    return Item(name.strip(), float(price))


def load(text: str) -> list[Item]:
    return [parse_item(line) for line in text.splitlines() if line.strip()]


def cheapest(items: list[Item]) -> Item | None:
    if not items:
        return None
    return min(items, key=lambda item: item.price)


def total(items: list[Item]) -> float:
    return sum(item.price for item in items)


def summary(items: list[Item]) -> str:
    best = cheapest(items)
    return f"{len(items)} items, {total(items):.2f} in total, cheapest: {best.name}"

test_main.py

from main import load, summary


def test_summary():
    """Zwei Artikel ergeben Anzahl, Summe und den Namen des günstigsten"""
    got = summary(load("pen; 1.50\nbook; 12\n"))
    assert got == "2 items, 13.50 in total, cheapest: pen", f"summary lieferte {got!r}"


def test_empty():
    """Eine leere Liste ergibt "no items\""""
    got = summary([])
    assert got == "no items", f"summary([]) lieferte {got!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 3 von 4

Projektschritt 3: die API aus ihren Annotationen dokumentieren

Schreiben Sie signature_table(functions): für jede Funktion eine Zeile wie parse_item(line: str) -> Item, gebaut aus annotationlib.get_annotations mit format=Format.STRING. Jede übergebene Funktion hat eine Rückgabe-Annotation. Es muss auch für Funktionen funktionieren, deren Annotationen Klassen nennen, die es zur Laufzeit nicht gibt.

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

    hints = annotationlib.get_annotations(function, format=annotationlib.Format.STRING) liefert ein Dict aus Strings.

  2. Hinweis 2

    hints.pop("return") entfernt die Rückgabe-Annotation, der Rest sind die Parameter, in ihrer Reihenfolge.

  3. Hinweis 3

    Verbinden Sie die Parameter mit ", ".join(f"{name}: {hint}" for name, hint in hints.items()) und nutzen Sie function.__name__ für den Namen.

Eine Lösung zeigen

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

import annotationlib
from collections.abc import Callable


class Item:
    def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:
        self.name = name
        self.price = price
        self.tags = tags or []


def parse_item(line: str) -> Item:
    name, price = line.split(";")
    return Item(name.strip(), float(price))


def load(text: str) -> list[Item]:
    return [parse_item(line) for line in text.splitlines() if line.strip()]


def cheapest(items: list[Item]) -> Item | None:
    if not items:
        return None
    return min(items, key=lambda item: item.price)


def total(items: list[Item]) -> float:
    return sum(item.price for item in items)


def summary(items: list[Item]) -> str:
    best = cheapest(items)
    if best is None:
        return "no items"
    return f"{len(items)} items, {total(items):.2f} in total, cheapest: {best.name}"


def signature_table(functions: list[Callable[..., object]]) -> list[str]:
    rows: list[str] = []
    for function in functions:
        hints = annotationlib.get_annotations(function, format=annotationlib.Format.STRING)
        returns = hints.pop("return")
        params = ", ".join(f"{name}: {hint}" for name, hint in hints.items())
        rows.append(f"{function.__name__}({params}) -> {returns}")
    return rows
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

import annotationlib
from collections.abc import Callable


class Item:
    def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:
        self.name = name
        self.price = price
        self.tags = tags or []


def parse_item(line: str) -> Item:
    name, price = line.split(";")
    return Item(name.strip(), float(price))


def load(text: str) -> list[Item]:
    return [parse_item(line) for line in text.splitlines() if line.strip()]


def cheapest(items: list[Item]) -> Item | None:
    if not items:
        return None
    return min(items, key=lambda item: item.price)


def total(items: list[Item]) -> float:
    return sum(item.price for item in items)


def summary(items: list[Item]) -> str:
    best = cheapest(items)
    if best is None:
        return "no items"
    return f"{len(items)} items, {total(items):.2f} in total, cheapest: {best.name}"


def signature_table(functions: list[Callable[..., object]]) -> list[str]:
    return []

test_main.py

from main import cheapest, parse_item, signature_table, summary


def test_table():
    """Eine Zeile je Funktion, mit Parameter- und Rückgabe-Annotationen"""
    got = signature_table([parse_item, cheapest, summary])
    expected = ["parse_item(line: str) -> Item", "cheapest(items: list[Item]) -> Item | None", "summary(items: list[Item]) -> str"]
    assert got == expected, f"signature_table lieferte {got!r}"


def test_undefined_names():
    """Undefinierte Namen in Annotationen lösen keine Ausnahme aus"""

    def later(x: Missing) -> Missing2:
        pass

    got = signature_table([later])
    assert got == ["later(x: Missing) -> Missing2"], f"signature_table lieferte {got!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 4 von 4

Projektschritt 4: mypy --strict

Führen Sie diese Übung mit Ihrem eigenen Python aus: python -m pip install mypy, dann python -m mypy --strict main.py. Es meldet vier Fehler: ein unnötiges # type: ignore, ein nacktes list und die neue untypisierte Hilfsfunktion label, die labels aufruft. Beheben Sie sie, ohne das Verhalten zu ändern, bis mypy --strict keine Probleme meldet.

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

Hinweise
  1. Hinweis 1

    Unused "type: ignore" comment: Das ignore in der min()-Zeile wird nicht mehr gebraucht, löschen Sie es.

  2. Hinweis 2

    Missing type arguments for generic type "list": Schreiben Sie wieder list[Callable[..., object]] und list[str].

  3. Hinweis 3

    Function is missing a type annotation: def label(item: Item) -> str:. Das behebt auch den Aufruf aus labels.

Eine Lösung zeigen

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

import annotationlib
from collections.abc import Callable


class Item:
    def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:
        self.name = name
        self.price = price
        self.tags = tags or []


def parse_item(line: str) -> Item:
    name, price = line.split(";")
    return Item(name.strip(), float(price))


def load(text: str) -> list[Item]:
    return [parse_item(line) for line in text.splitlines() if line.strip()]


def cheapest(items: list[Item]) -> Item | None:
    if not items:
        return None
    return min(items, key=lambda item: item.price)


def total(items: list[Item]) -> float:
    return sum(item.price for item in items)


def summary(items: list[Item]) -> str:
    best = cheapest(items)
    if best is None:
        return "no items"
    return f"{len(items)} items, {total(items):.2f} in total, cheapest: {best.name}"


def signature_table(functions: list[Callable[..., object]]) -> list[str]:
    rows: list[str] = []
    for function in functions:
        hints = annotationlib.get_annotations(function, format=annotationlib.Format.STRING)
        returns = hints.pop("return")
        params = ", ".join(f"{name}: {hint}" for name, hint in hints.items())
        rows.append(f"{function.__name__}({params}) -> {returns}")
    return rows


def label(item: Item) -> str:
    return f"{item.name} ({item.price:.2f})"


def labels(items: list[Item]) -> list[str]:
    return [label(item) for item in items]
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

import annotationlib
from collections.abc import Callable


class Item:
    def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:
        self.name = name
        self.price = price
        self.tags = tags or []


def parse_item(line: str) -> Item:
    name, price = line.split(";")
    return Item(name.strip(), float(price))


def load(text: str) -> list[Item]:
    return [parse_item(line) for line in text.splitlines() if line.strip()]


def cheapest(items: list[Item]) -> Item | None:
    if not items:
        return None
    return min(items, key=lambda item: item.price)  # type: ignore


def total(items: list[Item]) -> float:
    return sum(item.price for item in items)


def summary(items: list[Item]) -> str:
    best = cheapest(items)
    if best is None:
        return "no items"
    return f"{len(items)} items, {total(items):.2f} in total, cheapest: {best.name}"


def signature_table(functions: list) -> list:
    rows: list[str] = []
    for function in functions:
        hints = annotationlib.get_annotations(function, format=annotationlib.Format.STRING)
        returns = hints.pop("return")
        params = ", ".join(f"{name}: {hint}" for name, hint in hints.items())
        rows.append(f"{function.__name__}({params}) -> {returns}")
    return rows


def label(item):
    return f"{item.name} ({item.price:.2f})"


def labels(items: list[Item]) -> list[str]:
    return [label(item) for item in items]

test_main.py

from main import labels, load, summary


def test_mypy_strict():
    """mypy --strict meldet keine Fehler für main.py"""
    from mypy import api

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


def test_labels():
    """labels liefert den Namen und den Preis mit zwei Nachkommastellen"""
    got = labels(load("pen; 1.50\nbook; 12\n"))
    assert got == ["pen (1.50)", "book (12.00)"], f"labels lieferte {got!r}"


def test_summary_still_works():
    """summary ist unverändert"""
    assert summary([]) == "no items", "summary([]) sollte weiterhin 'no items' liefern"

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

VALUE-Annotationen mit einem TYPE_CHECKING-Import lesen

from annotationlib import get_annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from decimal import Decimal


def total(amount: Decimal) -> Decimal:
    return amount


print(get_annotations(total))

Was Python ausgibt

NameError: name 'Decimal' is not defined

Warum, und die Lösung

Decimal existiert nur für den Type Checker, also scheitert das Auswerten der Annotation. Fordern Sie ein Format an, das den Namen nicht braucht: get_annotations(total, format=Format.FORWARDREF) oder format=Format.STRING. Braucht Ihr Werkzeug wirklich die Klasse, importieren Sie sie normal.

Annotationen mit dem __future__-Import als Konverter nutzen

from __future__ import annotations

from annotationlib import get_annotations


def scale(n: int) -> int:
    return n


converters = get_annotations(scale)
print(converters["n"]("3"))

Was Python ausgibt

TypeError: 'str' object is not callable

Warum, und die Lösung

Der __future__-Import speichert jede Annotation als String, converters["n"] ist also 'int', nicht die Klasse. Entfernen Sie den Import in Python 3.14: Annotationen sind ohnehin verzögert. Muss er bleiben, rufen Sie get_annotations(scale, eval_str=True) auf.

Ein falsch geschriebener Name, der erst später scheitert

from typing import get_type_hints


def total(prices: List[float]) -> float:
    return sum(prices)


print(total([1.5, 2.5]))
print(get_type_hints(total))

Was Python ausgibt

NameError: name 'List' is not defined

Warum, und die Lösung

Das def und der Aufruf funktionieren, weil nichts die Annotation liest. Der Fehler erscheint erst, wenn ein Werkzeug sie auswertet, vielleicht weit entfernt. Schreiben Sie list[float] oder importieren Sie List aus typing, und führen Sie mypy aus, das in der def-Zeile Name "List" is not defined meldet.

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

Annotationen werden verzögert ausgewertet

Seit Python 3.14 (PEP 649) speichert def f(a: Cls) eine kleine Funktion, statt Cls auszuwerten. Der Ausdruck läuft, wenn etwas die Annotationen zum ersten Mal liest, und das Ergebnis wird zwischengespeichert. So kann eine Klasse auf sich selbst verweisen (next: Node | None) und eine Funktion eine weiter unten definierte Klasse nennen, ohne Anführungszeichen. Ein falsch geschriebener Name scheitert nicht mehr beim def, sondern erst, wenn ein Werkzeug die Annotationen liest. Mit from __future__ import annotations werden Annotationen stattdessen als Strings gespeichert.

annotationlib.get_annotations und Format

annotationlib.get_annotations(obj) funktioniert für Funktionen, Klassen und Module und ersetzt das direkte Lesen von __annotations__. Das Argument format bestimmt, was Sie bekommen. Format.VALUE, der Standard, wertet alles aus und löst NameError für einen Namen aus, den es zur Laufzeit nicht gibt, etwa einen, der nur unter TYPE_CHECKING importiert wird. Format.FORWARDREF liefert echte Objekte, wo es geht, und sonst ForwardRef-Stellvertreter. Format.STRING liefert den Quelltext. Dokumentationswerkzeuge wollen STRING, Werkzeuge, die echte Klassen brauchen, VALUE.

Ein Modul typisieren, bis mypy sauber ist

Ergänzen Sie Hints von unten nach oben: zuerst Parameter- und Rückgabetypen kleiner Hilfsfunktionen, dann die Funktionen, die sie aufrufen. mypy findet dann Fehler, wo Hint und Code nicht zusammenpassen, etwa wenn ein Ergebnis None sein kann. Standardmäßig überspringt mypy die Rümpfe nicht annotierter Funktionen; --check-untyped-defs prüft sie, und --strict verlangt zusätzlich Annotationen für jede Funktion, verbietet nackte Generics wie list und meldet unnötige # type: ignore-Kommentare.

Quellen

Zuletzt geprüft am 29. September 2026