Cuando una clase existe solo para guardar datos, escribir __init__, __repr__ y __eq__ a mano es boilerplate puro. Las dataclasses lo generan por ti.
@dataclass: datos sin boilerplate
from dataclasses import dataclass
@dataclass
class Punto:
x: float
y: float
p = Punto(1.5, 2.5)
print(p) # Punto(x=1.5, y=2.5) ← __repr__ gratis
print(p == Punto(1.5, 2.5)) # True ← __eq__ gratis
El decorador genera automáticamente:
__init__con los campos anotados__repr__legible para debugging__eq__que compara campo por campo
Valores por defecto
@dataclass
class Usuario:
nombre: str
activo: bool = True
etiquetas: list[str] = field(default_factory=list)
La trampa importante: para valores mutables por defecto (listas, dicts, sets) usa field(default_factory=list). Un = [] directo se evaluaría UNA vez y todas las instancias compartirían la misma lista.
Validez congelada: frozen
@dataclass(frozen=True)
class Moneda:
codigo: str
dolar = Moneda("USD")
dolar.codigo = "EUR" # FrozenInstanceError: inmutable
Con frozen=True la instancia es inmutable — perfecta como clave de diccionario o para constantes.
@property: atributos calculados
Una property expone un método como si fuera un atributo:
class Rectangulo:
def __init__(self, ancho, alto):
self.ancho = ancho
self.alto = alto
@property
def area(self):
return self.ancho * self.alto
r = Rectangulo(3, 5)
print(r.area) # 15 ← SIN paréntesis: parece atributo, es un cálculo
¿Por qué y no un método area()? Semántica: si es una característica del objeto (y no una acción), property. Y puedes empezar con atributo simple y migrar a property sin romper a quien usa tu clase.
Properties con validación (setter)
class Temperatura:
def __init__(self):
self._celsius = 0 # el guion bajo dice: "uso interno"
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, valor):
if valor < -273.15:
raise ValueError("Debajo del cero absoluto: imposible")
self._celsius = valor
t = Temperatura()
t.celsius = 25 # pasa por el setter
t.celsius = -500 # ValueError
La convención _celsius (un guion bajo) señala "no toques esto directamente": quien usa tu clase pasa por la property, que valida.
Resumen
@dataclassgenera__init__,__repr__y__eq__desde las anotaciones- Defaults mutables →
field(default_factory=list), nunca= [] frozen=Truehace la instancia inmutable@propertyexpone cálculos como atributos; elsetteragrega validación_atributocon guion bajo: convención de "interno, no lo uses"