Modelos de Datos con Dataclasses
Al programar en Python orientado a objetos, con frecuencia creamos clases cuyo propósito principal es almacenar datos. En el enfoque tradicional, esto requiere escribir código repetitivo (boilerplate) para implementar __init__, __repr__, __eq__, etc.
Introducido en Python 3.7 (PEP 557), el decorador @dataclass del módulo estándar dataclasses automatiza la generación de estos métodos especiales a partir de las anotaciones de tipo de los atributos.
1. Comparación: Clase Tradicional vs @dataclass #
Clase tradicional (mucho código repetitivo): #
1class UsuarioTradicional: 2 def __init__(self, id_usuario: int, nombre: str, email: str): 3 self.id_usuario = id_usuario 4 self.nombre = nombre 5 self.email = email 6 7 def __repr__(self) -> str: 8 return f"UsuarioTradicional(id={self.id_usuario}, nombre='{self.nombre}', email='{self.email}')" 9 10 def __eq__(self, other: object) -> bool: 11 if not isinstance(other, UsuarioTradicional): 12 return False 13 return (self.id_usuario, self.nombre, self.email) == (other.id_usuario, other.nombre, other.email)
Con @dataclass (conciso, legible y declarativo): #
1from dataclasses import dataclass 2 3@dataclass 4class Usuario: 5 id_usuario: int 6 nombre: str 7 email: str 8 activo: bool = True # Valor por defecto 9 10user1 = Usuario(1, "Ana López", "ana@example.com") 11user2 = Usuario(1, "Ana López", "ana@example.com") 12 13print(user1) # __repr__ automático 14print(user1 == user2) # __eq__ automático por valorSalida de consola Usuario(id_usuario=1, nombre='Ana López', email='ana@example.com', activo=True) True
2. Atributos mutables por defecto con field(default_factory=...) #
En Python no se deben asignar estructuras mutables (como [] o {}) directamente como valor por defecto en clases. Con dataclasses se utiliza la función field:
1from dataclasses import dataclass, field 2 3@dataclass 4class Curso: 5 titulo: str 6 lecciones: list[str] = field(default_factory=list) 7 tags: set[str] = field(default_factory=set) 8 9curso_python = Curso("Python Avanzado") 10curso_python.lecciones.append("Dataclasses") 11print(curso_python)Salida de consola Curso(titulo='Python Avanzado', lecciones=['Dataclasses'], tags=set())
3. Inmutabilidad y Clases Hashables (frozen=True) #
Al configurar frozen=True, la dataclass se vuelve de solo lectura (inmutable), lo que permite usar sus instancias de forma segura como claves de diccionarios o elementos de conjuntos (sets):
1@dataclass(frozen=True) 2class Coordenada: 3 latitud: float 4 longitud: float 5 6coord = Coordenada(40.4168, -3.7038) 7# coord.latitud = 41.0 -> Lanzará FrozenInstanceError 8puntos_interes = {coord: "Madrid Centro"} 9print(puntos_interes[coord])Salida de consola Madrid Centro
4. Comparación y Ordenación (order=True) #
Podemos habilitar métodos de comparación como <, <=, >, >= automáticamente:
1@dataclass(order=True) 2class Tarea: 3 prioridad: int 4 descripcion: str = field(compare=False) # Se excluye de la comparación 5 6t1 = Tarea(1, "Revisar logs") 7t2 = Tarea(5, "Planificar sprint") 8 9print(t1 < t2)Salida de consola True
5. Post-inicialización con __post_init__ #
Si necesitas calcular atributos derivados o validar datos al instanciar la clase:
1@dataclass 2class Producto: 3 nombre: str 4 precio_base: float 5 descuento_porcentaje: float = 0.0 6 precio_final: float = field(init=False) 7 8 def __post_init__(self): 9 if self.precio_base < 0: 10 raise ValueError("El precio base no puede ser negativo") 11 self.precio_final = self.precio_base * (1 - self.descuento_porcentaje / 100) 12 13prod = Producto("Teclado Mecánico", 100.0, 15.0) 14print(f"Precio final con descuento: {prod.precio_final} €")Salida de consola Precio final con descuento: 85.0 €
Resumen del tema
Conceptos clave #
@dataclass: genera automáticamente__init__,__repr__y__eq__a partir de campos tipados.field(default_factory=...): asigna valores por defecto mutables (listas, dicts) de forma aislada para cada instancia.frozen=True: crea objetos inmutables y hashables para sets y claves de diccionarios.order=True: genera métodos de comparación ordenando por los campos en orden de declaración.__post_init__: método especial ejecutado inmediatamente después de__init__para validación y cómputo de campos derivados.
Qué debes recordar #
Usa @dataclass para modelar estructuras de datos limpias, libres de boilerplate y completamente tipadas en Python.