Typannotationen sind Dokumentation, die ein Werkzeug prüfen kann
Python ist dynamisch typisiert: Eine Variable kann alles enthalten, und ein falscher Typ zeigt sich erst, wenn die Zeile läuft, vielleicht mitten im Handelstag. Typannotationen halten fest, was eine Funktion erwartet und liefert: def mid_price(bid: float, ask: float) -> float. Python selbst ignoriert sie bei der Ausführung. Ihr Wert: Ein Editor und ein statischer Prüfer wie mypy oder pyright lesen sie und warnen vor einem falschen Aufruf, bevor der Code je läuft, und sie dokumentieren die Funktion besser als ein veraltender Kommentar.
# Hints describe the function; Python does not enforce them when it runs
def mid_price(bid: float, ask: float) -> float:
return (bid + ask) / 2
print(mid_price(100.0, 100.5)) # 100.25
print(mid_price.__annotations__) # {'bid': <class 'float'>, 'ask': <class 'float'>, 'return': <class 'float'>}
print(mid_price(10, 12)) # 11.0 an int is accepted where float is hinted
try:
mid_price("a", "b") # a checker flags this line before the code runs
except TypeError as error:
print("only fails when it runs:", error) # only fails when it runs: unsupported operand type(s) for /Container, None und Unions
Eingebaute Container nehmen ihre Elementtypen in eckigen Klammern: list[float], dict[str, float], tuple[str, float] (ein Typ pro Position). Ein Wert, der fehlen kann, ist float | None, was den Aufrufer zwingt, None zu behandeln; der Prüfer beschwert sich, wenn er das Ergebnis ungeprüft als Zahl nutzt. In der Funktion verengt ein if x is None: return den Typ, sodass der Rest weiß, dass es ein float ist. Für nur gelesene Argumente bevorzugen Sie einen allgemeinen Typ wie Sequence[float] (Listen, Tupel) statt list[float], damit mehr Aufrufer passen; liefern Sie einen konkreten Typ wie list[float].
# Containers, an optional result, and narrowing with an is None check
def best_quote(quotes: list[tuple[str, float]]) -> tuple[str, float] | None:
if not quotes:
return None
return min(quotes, key=lambda quote: quote[1])
def describe(quotes: list[tuple[str, float]]) -> str:
best = best_quote(quotes)
if best is None: # after this check the checker knows best is a tuple
return "no quotes"
venue, price = best
return f"{venue} at {price:.2f}"
print(describe([("XNAS", 100.02), ("ARCX", 100.01)])) # ARCX at 100.01
print(describe([])) # no quotesDatenformen und der Prüfer bei der Arbeit
Ein Dictionary mit festen Schlüsseln, etwa eine Order, beschreibt man besser, als es dict zu lassen. Ein TypedDict oder eine dataclass benennt jedes Feld und seinen Typ, sodass ein Tippfehler wie order["quantiy"] im Editor ein Fehler ist statt eines KeyError um 15:59. Den Prüfer startet man auf der Kommandozeile (mypy main.py); er gibt jede Stelle aus, an der der Code seinen Annotationen widerspricht, und endet mit Fehlercode, was in die automatischen Prüfungen passt, die später kommen. Annotationen lassen sich schrittweise ergänzen: Any schaltet die Prüfung für einen Wert ab, ungetypter Code wird einfach übersprungen. Annotationen sind auch Daten, die Werkzeuge zur Laufzeit mit typing.get_type_hints lesen, genau so prüft dieses Lab sie.
# A TypedDict names the fields of an order; the checker would flag order["quantiy"]
from typing import TypedDict
class Order(TypedDict):
symbol: str
quantity: int
price: float
def notional(order: Order) -> float:
return order["quantity"] * order["price"]
order: Order = {"symbol": "AAPL", "quantity": 100, "price": 184.5}
print(notional(order)) # 18450.0
print(Order.__annotations__) # {'symbol': <class 'str'>, 'quantity': <class 'int'>, 'price': <class 'float'>}# The hints can be read at run time, which is how tools (and this lab) inspect a function
import typing
def apply_fee(prices: typing.Sequence[float], fee_rate: float = 0.001) -> list[float]:
return [price * (1 + fee_rate) for price in prices]
hints = typing.get_type_hints(apply_fee)
print(hints["fee_rate"], hints["return"]) # <class 'float'> list[float]
print(typing.get_origin(hints["prices"]).__name__) # Sequence
print(apply_fee((100.0, 200.0))) # a tuple is accepted: [100.1, 200.2]- Annotationen (
bid: float,-> float) dokumentieren eine Funktion und werden bei der Ausführung ignoriert; ein Prüfer wie mypy liest sie. - Container nehmen Elementtypen (
list[tuple[str, float]]); ein fehlender Wert istfloat | None; nur gelesene Argumente können ein allgemeinesSequence[float]sein. - Nutzen Sie
TypedDictoderdataclassfür Datensätze, lassen Sie den Prüfer in Ihren automatischen Prüfungen laufen und validieren Sie weiter externe Daten und testen Sie die Zahlen.
Als Nächstes: Testen mit pytest, Fixtures, parametrisierte Fälle und die Prüfungen, die eine Strategie ehrlich halten.
Lab: Was bewertet wird
- Zahlen in
mid_priceannotieren25 Pkt. - Einen Container in
best_quotetypisieren25 Pkt. - Einen fehlenden Wert in
safe_ratiozulassen25 Pkt. - Jede Sequenz in
apply_feeakzeptieren25 Pkt.