Menos código repetitivo para clases que guardan datos
Muchas clases existen sobre todo para guardar unos pocos campos: una operación, una cotización, una configuración. Escribir __init__, __repr__ y __eq__ para cada una es repetitivo. El decorador @dataclass del módulo dataclasses los escribe por ti a partir de una lista de campos anotados. Declaras symbol: str, price: float, quantity: int una vez y obtienes un constructor Trade("AAPL", 184.25, 200), un repr legible como Trade(symbol='AAPL', price=184.25, quantity=200) y una igualdad campo a campo. Las anotaciones documentan la intención; Python no las impone.
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.0Valores por defecto, field() y valores mutables
Un campo puede tener un valor por defecto (currency: str = "USD"), y los campos con valor por defecto deben ir después de los que no lo tienen. Para un valor por defecto mutable como una lista, un simple = [] lo compartirían todos los objetos (y Python lo rechaza); escribe trades: list = field(default_factory=list) para que cada objeto reciba su propia lista nueva. field(...) permite también ocultar un campo del repr (repr=False) o de las comparaciones (compare=False).
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=[])Tus propios métodos, __post_init__ y constructores alternativos
Una dataclass sigue siendo una clase normal, así que puedes añadir métodos. __post_init__(self) se ejecuta justo después del __init__ generado, el lugar para validar o limpiar valores (self.symbol = self.symbol.upper(), raise ValueError(...) para un mal precio). Un @classmethod recibe la clase como cls y es la forma habitual de construir un objeto desde otra forma, como texto: Trade.from_line("AAPL,184.25,200") devuelve cls(symbol, float(price), int(quantity)), así la validación sigue aplicándose y las subclases también funcionan.
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.0Congeladas, ordenadas y cuándo ganan las clases normales
@dataclass(frozen=True) hace que las instancias sean de solo lectura (asignar a un campo lanza FrozenInstanceError) y les da un hash, así que pueden ser claves de diccionario o miembros de conjunto: ideal para valores que no deben cambiar, como la especificación de un contrato. @dataclass(order=True) añade <, <=, >, >= comparando los campos en orden. Usa una dataclass para registros de datos; para objetos con mucho comportamiento y estado oculto, una clase escrita a mano con métodos cuidados es más clara.
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 no impiden crear Trade("AAPL", "abc", 1). Si valores erróneos corrompieran en silencio cálculos posteriores, compruébalos en __post_init__ y lanza ValueError, de modo que un registro roto no pueda existir. Crea siempre los objetos mediante el constructor normal, también desde from_line, para que la comprobación no se pueda saltar.@dataclassconvierte campos anotados en__init__,__repr__y__eq__.- Los valores por defecto van al final; usa
field(default_factory=list)para listas y diccionarios. __post_init__valida;@classmethodconstruye desde otras formas;frozen=Truehace los valores de solo lectura.
A continuación: derivadas en código, estimar con qué rapidez cambia una magnitud, la idea detrás de sensibilidades de opciones como delta y gamma.
Lab: qué se califica
- Limpiar y validar una operación con
__post_init__25 pts - Calcular el valor negociado con
notional25 pts - Construir una operación desde texto con
from_line25 pts - Netear las operaciones de un símbolo con
net_quantity25 pts