Las dataclasses reducen el código repetitivo de clases cuyo propósito principal es almacenar datos. Con el decorador @dataclass, Python puede generar automáticamente métodos como __init__, __repr__ y __eq__ a partir de anotaciones de tipo. Esto mejora la legibilidad y evita errores al mantener constructores manuales con muchos atributos.
Una dataclass sigue siendo una clase normal: puede incluir métodos, propiedades, herencia y validación. Para comprender la base, consulta la guía de programación orientada a objetos en Python.
Crear la primera dataclass
from dataclasses import dataclass
@dataclass
class Producto:
nombre: str
precio: float
stock: int
producto = Producto("Teclado", 49.90, 10)
print(producto)
print(producto.nombre)El decorador genera un constructor con los tres campos, una representación legible y comparación por valores. Las anotaciones describen la intención, pero Python no valida automáticamente los tipos en tiempo de ejecución.
Valores predeterminados
@dataclass
class Usuario:
nombre: str
activo: bool = True
intentos: int = 0Los campos sin valor predeterminado deben aparecer antes de los campos con valor predeterminado. Esta regla evita una firma de función inválida.
Evitar valores mutables compartidos
No utilices una lista literal como valor predeterminado. Emplea default_factory:
from dataclasses import dataclass, field
@dataclass
class Pedido:
cliente: str
productos: list[str] = field(default_factory=list)Cada instancia recibe una lista nueva. Este patrón evita que dos pedidos compartan accidentalmente la misma colección.
Personalizar campos con field
field() permite excluir un atributo de la representación, comparación o constructor:
@dataclass
class Cuenta:
usuario: str
token: str = field(repr=False)
id_interno: int = field(init=False, default=0)repr=False evita mostrar el token en la representación, aunque no sustituye controles de seguridad. init=False impide que el campo sea un parámetro del constructor.
Validar con __post_init__
__post_init__() se ejecuta después del constructor generado:
@dataclass
class Producto:
nombre: str
precio: float
stock: int = 0
def __post_init__(self) -> None:
if not self.nombre.strip():
raise ValueError("El nombre es obligatorio")
if self.precio < 0:
raise ValueError("El precio no puede ser negativo")
if self.stock < 0:
raise ValueError("El stock no puede ser negativo")Utiliza este método para invariantes sencillas y conversiones controladas. Si la validación requiere servicios externos o muchas reglas, considera una capa específica.
Campos calculados
@dataclass
class Rectangulo:
ancho: float
alto: float
area: float = field(init=False)
def __post_init__(self) -> None:
self.area = self.ancho * self.altoSi el área debe actualizarse cuando cambian ancho o alto, una propiedad calculada puede ser mejor que almacenar el resultado.
Dataclasses inmutables
@dataclass(frozen=True)
class Punto:
x: float
y: floatfrozen=True impide la asignación normal después de crear la instancia. Es una protección útil contra cambios accidentales, pero no convierte automáticamente en inmutables los objetos mutables contenidos.
Ordenación de instancias
@dataclass(order=True)
class Tarea:
prioridad: int
titulo: strorder=True genera comparaciones siguiendo el orden de los campos. Si solo deseas ordenar por prioridad y no por título, excluye el segundo campo:
@dataclass(order=True)
class Tarea:
prioridad: int
titulo: str = field(compare=False)Campos que son solo argumentos
InitVar permite recibir un valor en el constructor sin guardarlo como campo:
from dataclasses import InitVar, dataclass
@dataclass
class Perfil:
nombre: str
clave_temporal: InitVar[str]
def __post_init__(self, clave_temporal: str) -> None:
if len(clave_temporal) < 8:
raise ValueError("Clave temporal demasiado corta")Evita utilizar este patrón para conservar secretos en memoria sin necesidad.
Convertir a diccionario o tupla
from dataclasses import asdict, astuple
datos = asdict(producto)
valores = astuple(producto)asdict() convierte dataclasses anidadas de forma recursiva y puede copiar estructuras. Para objetos grandes o serialización controlada, construye explícitamente el esquema esperado.
Reemplazar campos sin modificar el original
from dataclasses import replace
actualizado = replace(producto, precio=44.90)replace() crea una nueva instancia y vuelve a ejecutar la inicialización correspondiente. Es especialmente útil con dataclasses congeladas.
Herencia
@dataclass
class Persona:
nombre: str
@dataclass
class Empleado(Persona):
departamento: str
salario: floatLos campos heredados participan en el constructor. Debes respetar el orden entre campos obligatorios y predeterminados a través de la jerarquía. Evita árboles complejos cuando la composición sea más clara.
slots para reducir memoria
@dataclass(slots=True)
class Coordenada:
latitud: float
longitud: floatslots=True puede reducir memoria y evitar atributos arbitrarios. Revisa la compatibilidad con herencia, serialización y bibliotecas que esperan __dict__.
Dataclass frente a namedtuple y diccionario
Un diccionario es flexible, pero no declara un esquema claro. Una namedtuple es ligera e inmutable, pero ofrece menos posibilidades de validación y valores predeterminados. Una dataclass funciona bien para entidades internas, parámetros y resultados estructurados. Para estudiar anotaciones, revisa type hints en Python.
Dataclass frente a modelos de validación
Las anotaciones no convierten automáticamente strings en números ni validan datos JSON. Cuando los datos provienen de usuarios o APIs, una biblioteca de validación puede ser más adecuada. Las dataclasses son excelentes para representar datos ya confiables dentro de la aplicación.
Añadir métodos y documentación
@dataclass
class Producto:
# Representa un artículo disponible en inventario.
nombre: str
precio: float
def aplicar_descuento(self, porcentaje: float) -> float:
return self.precio * (1 - porcentaje / 100)La guía de docstrings en Python ayuda a documentar responsabilidades y parámetros. El decorador de dataclass también se relaciona con la guía de decoradores en Python.
Enums y estados
Cuando un campo acepta un conjunto cerrado de estados, utiliza un Enum en lugar de strings libres. La guía de Enums en Python muestra cómo evitar valores mágicos.
Fuentes oficiales
La documentación oficial de dataclasses describe todos los parámetros del decorador y de field(). El PEP 557 explica las motivaciones y decisiones de diseño originales.
Errores frecuentes
Los problemas más comunes son usar listas como valores predeterminados, creer que los tipos se validan solos, exponer secretos mediante repr, almacenar campos calculados que quedan obsoletos y activar order=True sin pensar en el orden semántico. También debes evaluar si una clase con mucha lógica sigue siendo principalmente una clase de datos.
Conclusión
Las dataclasses eliminan repetición sin quitar el poder de las clases. Declara campos con tipos, utiliza default_factory para mutables, valida invariantes en __post_init__ y elige frozen, order o slots solo cuando aporten valor. Su mayor beneficio no es ahorrar líneas, sino hacer explícita la estructura de los datos y mantener coherentes la creación, comparación y representación de objetos.






