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))// A2.6 · ca. 45 Min. · Vertiefung
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
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
def half(x: int) -> int:
return x / 2
print(half(3))Vorhersagen · Aufgabe 2 von 7
def func(a: Cls) -> None:
print(a)
class Cls:
pass
print(func.__annotations__)Üben · Aufgabe 3 von 7
get_annotations(f, format=Format.____)Üben · Aufgabe 4 von 7
def expensive():
print("evaluating", end=" ")
return int
def f(x: expensive()) -> None:
pass
print("defined", end=" ")
f.__annotations__
f.__annotations__
print("done")Üben · Aufgabe 5 von 7
Denksport · Aufgabe 6 von 7
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)Anwenden · Aufgabe 7 von 7
Prüfen Sie Ihr Ergebnis anhand dieser Liste
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
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.pyAusgabe
{'value': <class 'int'>, 'next': __main__.Node | None, 'return': None}
{'amount': 'Decimal', 'return': 'str'}
ForwardRef Decimal
VALUE: name 'Decimal' is not definedTab 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.
Übung 1 von 4
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.
Methoden brauchen -> None, wenn sie nichts zurückgeben: def __init__(self, name: str, price: float, tags: list[str] | None = None) -> None:
Item ist über den Funktionen definiert, und selbst wenn nicht, würde 3.14 die Hints verzögert auswerten.
cheapest liefert None für eine leere Liste, also ist sein Rückgabe-Hint Item | None.
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)
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 2 von 4
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.
cheapest liefert Item | None, und None hat kein Attribut name.
Speichern Sie das Ergebnis in best und prüfen Sie dann if best is None: return "no items".
Nach diesem Test grenzt mypy best auf Item ein, und die letzte Zeile besteht die Typprüfung.
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}"
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 3 von 4
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.
hints = annotationlib.get_annotations(function, format=annotationlib.Format.STRING) liefert ein Dict aus Strings.
hints.pop("return") entfernt die Rückgabe-Annotation, der Rest sind die Parameter, in ihrer Reihenfolge.
Verbinden Sie die Parameter mit ", ".join(f"{name}: {hint}" for name, hint in hints.items()) und nutzen Sie function.__name__ für den Namen.
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
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 4 von 4
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.
Unused "type: ignore" comment: Das ignore in der min()-Zeile wird nicht mehr gebraucht, löschen Sie es.
Missing type arguments for generic type "list": Schreiben Sie wieder list[Callable[..., object]] und list[str].
Function is missing a type annotation: def label(item: Item) -> str:. Das behebt auch den Aufruf aus labels.
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]
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.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenfrom 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 definedWarum, 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.
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 callableWarum, 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.
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 definedWarum, 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
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.