Weniger Boilerplate für Klassen, die Daten halten
Viele Klassen existieren vor allem, um ein paar Felder zu halten: einen Trade, einen Kurs, eine Konfiguration. __init__, __repr__ und __eq__ für jede zu schreiben ist repetitiv. Der Decorator @dataclass aus dem Modul dataclasses schreibt sie aus einer Liste annotierter Felder für Sie. Sie deklarieren symbol: str, price: float, quantity: int einmal und erhalten einen Konstruktor Trade("AAPL", 184.25, 200), ein lesbares repr wie Trade(symbol='AAPL', price=184.25, quantity=200) und feldweise Gleichheit. Die Annotationen dokumentieren die Absicht; Python erzwingt sie nicht.
from dataclasses import dataclass
@dataclass
class Trade:
symbol: str
price: float
quantity: int
a = Trade("AAPL", 184.25, 200)
b = Trade("AAPL", 184.25, 200)
print(a) # Trade(symbol='AAPL', price=184.25, quantity=200)
print(a == b) # True: compared field by field
print(a.price * a.quantity) # 36850.0Standardwerte, field() und veränderliche Werte
Ein Feld kann einen Standardwert haben (currency: str = "USD"), und Felder mit Standardwert müssen nach denen ohne stehen. Bei einem veränderlichen Standardwert wie einer Liste würde ein einfaches = [] von allen Objekten geteilt (und Python lehnt es ab); schreiben Sie trades: list = field(default_factory=list), damit jedes Objekt eine eigene neue Liste bekommt. Mit field(...) lässt sich ein Feld auch aus dem repr (repr=False) oder aus Vergleichen (compare=False) ausnehmen.
from dataclasses import dataclass, field
@dataclass
class Book:
name: str
currency: str = "USD" # a default
trades: list = field(default_factory=list) # a fresh list per object
one = Book("desk-a")
two = Book("desk-b")
one.trades.append(("AAPL", 100))
print(one) # Book(name='desk-a', currency='USD', trades=[('AAPL', 100)])
print(two.trades) # [] (not shared)
print(Book("x", "EUR")) # Book(name='x', currency='EUR', trades=[])Eigene Methoden, __post_init__ und alternative Konstruktoren
Eine Dataclass bleibt eine normale Klasse, Sie können also Methoden hinzufügen. __post_init__(self) läuft direkt nach dem generierten __init__, der richtige Ort, um Werte zu prüfen oder zu bereinigen (self.symbol = self.symbol.upper(), raise ValueError(...) bei schlechtem Preis). Eine @classmethod erhält die Klasse als cls und ist der übliche Weg, ein Objekt aus einer anderen Form zu bauen, etwa aus Text: Trade.from_line("AAPL,184.25,200") liefert cls(symbol, float(price), int(quantity)), sodass die Prüfung weiterläuft und auch Unterklassen funktionieren.
from dataclasses import dataclass
@dataclass
class Trade:
symbol: str
price: float
quantity: int
def __post_init__(self):
self.symbol = self.symbol.strip().upper()
if self.price <= 0:
raise ValueError("price must be positive")
def notional(self):
return self.price * abs(self.quantity)
@classmethod
def from_line(cls, line):
symbol, price, quantity = line.split(",")
return cls(symbol, float(price), int(quantity))
t = Trade.from_line(" aapl,184.25,-200")
print(t) # Trade(symbol='AAPL', price=184.25, quantity=-200)
print(t.notional()) # 36850.0Eingefroren, geordnet und wann normale Klassen gewinnen
@dataclass(frozen=True) macht Instanzen schreibgeschützt (Zuweisung an ein Feld löst FrozenInstanceError aus) und gibt ihnen einen Hash, sodass sie Dictionary-Schlüssel oder Mengenmitglieder sein können: ideal für Werte, die sich nicht ändern dürfen, etwa eine Kontraktspezifikation. @dataclass(order=True) fügt <, <=, >, >= hinzu, die die Felder der Reihe nach vergleichen. Nutzen Sie eine Dataclass für Datensätze; für Objekte mit viel Verhalten und verstecktem Zustand ist eine von Hand geschriebene Klasse mit sorgfältigen Methoden klarer.
from dataclasses import dataclass
@dataclass(frozen=True, order=True)
class Contract:
symbol: str
expiry: str # ISO dates sort correctly as text
a = Contract("ESZ5", "2025-12-19")
b = Contract("ESZ5", "2026-03-20")
print(a < b) # True
print({a: "front", b: "next"}[a]) # front (frozen => hashable)
try:
a.symbol = "NQZ5"
except Exception as error:
print(type(error).__name__) # FrozenInstanceErrorprice: float verhindern nicht, dass Trade("AAPL", "abc", 1) erzeugt wird. Würden schlechte Werte spätere Berechnungen stillschweigend verfälschen, prüfen Sie sie in __post_init__ und lösen ValueError aus, damit ein kaputter Datensatz gar nicht existieren kann. Erzeugen Sie Objekte stets über den normalen Konstruktor, auch aus from_line, damit die Prüfung nicht umgangen werden kann.@dataclassmacht aus annotierten Feldern__init__,__repr__und__eq__.- Standardwerte stehen zuletzt; nutzen Sie
field(default_factory=list)für Listen und Dictionaries. __post_init__prüft;@classmethodbaut aus anderen Formen;frozen=Truemacht Werte schreibgeschützt.
Als Nächstes: Ableitungen im Code, die Änderungsrate einer Größe schätzen, die Idee hinter Options-Sensitivitäten wie Delta und Gamma.
Lab: Was bewertet wird
- Mit
__post_init__einen Trade bereinigen und prüfen25 Pkt. - Mit
notionalden gehandelten Wert berechnen25 Pkt. - Mit
from_lineeinen Trade aus Text bauen25 Pkt. - Mit
net_quantitydie Trades eines Symbols saldieren25 Pkt.