@dataclass genera métodos como __init__, __repr__ y __eq__ a partir de campos anotados. Es apropiada para objetos que agrupan datos con un comportamiento sencillo, sin convertir cada clase en un modelo automático.

Crear un modelo seguro

from dataclasses import dataclass, field


@dataclass(frozen=True, slots=True)
class Pedido:
    codigo: str
    items: tuple[str, ...] = field(default_factory=tuple)

    def __post_init__(self) -> None:
        if not self.codigo.strip():
            raise ValueError("codigo vacio")


pedido = Pedido("A-42", ("libro", "curso"))
print(pedido)

El decorador lee los atributos anotados y crea el inicializador según el orden de los campos. Los obligatorios deben preceder a los que tienen valor predeterminado, incluso con herencia. kw_only=True obliga llamadas explícitas como Opciones(host="db", timeout=2.0) y evita que reordenar campos cambie argumentos silenciosamente.

Igualdad, orden y hash

Dos instancias de la misma clase son iguales por defecto si todos sus campos comparables tienen valores iguales. Es igualdad de valor, no identidad. order=True genera el orden y compara los campos como una tupla según su declaración.

from dataclasses import dataclass, field


@dataclass(order=True)
class Tarea:
    prioridad: int
    titulo: str = field(compare=False)


print(sorted([Tarea(3, "publicar"), Tarea(1, "revisar")]))

Excluir titulo es una decisión del dominio: tareas con igual prioridad ahora se comparan como iguales. No uses compare=False solo para satisfacer una prueba. Define qué campos constituyen el valor.

Una dataclass congelada normalmente puede recibir __hash__; una mutable permanece no hashable para proteger conjuntos y claves frente a cambios posteriores. unsafe_hash=True existe para casos especiales, pero modificar un campo comparado después de insertarlo puede romper las búsquedas.

Validar y calcular campos

__post_init__ se ejecuta después del inicializador y puede validar relaciones o completar un campo con init=False.

from dataclasses import dataclass, field
from decimal import Decimal


@dataclass(frozen=True)
class Item:
    cantidad: int
    precio: Decimal
    total: Decimal = field(init=False)

    def __post_init__(self) -> None:
        if self.cantidad <= 0 or self.precio < 0:
            raise ValueError("item invalido")
        object.__setattr__(
            self, "total", self.precio * self.cantidad
        )

object.__setattr__ hace falta porque la protección congelada ya está activa. Utiliza este escape únicamente para construir el valor derivado. Si la inicialización requiere red, base de datos o muchos caminos de recuperación, una función fábrica con nombre explica mejor el proceso y conserva un constructor determinista.

InitVar recibe un argumento en __init__ y lo pasa a __post_init__ sin almacenarlo. Sirve para una normalización breve; las conversiones complejas quedan más claras en una fábrica.

Copiar, convertir y elegir el modelo

Al revisar la clase, crea el caso válido más pequeño, compara instancias equivalentes e inspecciona repr. Confirma que los valores mutables usan fábricas y que los secretos se excluyen de la representación. Provoca cada invariante inválida y comprueba que el mensaje indica la corrección. Si el objeto será clave, garantiza que el estado comparado no cambia.

Observa la firma como usuario de la API. Demasiados opcionales pueden indicar conceptos mezclados; las banderas booleanas pueden expresarse mejor mediante un enum o fábricas nombradas. Prueba llamadas posicionales y nombradas, copia con replace() y conversión de objetos anidados. La dataclass reduce código ceremonial, pero no decide el contrato.

En pruebas de serialización, compara el resultado esperado e incluye fechas, decimales y enums. Ejecuta el verificador estático con una llamada deliberadamente incorrecta para confirmar la configuración. Mide slots con un volumen realista y conserva la versión sencilla cuando no exista un beneficio observable.

dataclasses.replace(pedido, codigo="A-42-OFERTA") crea otra instancia y repite la inicialización. Es una actualización práctica para valores congelados. asdict() convierte dataclasses anidadas y copia profundamente otros valores, pero no constituye un serializador JSON completo. Fechas, decimales, enums y objetos personalizados todavía necesitan una representación explícita. Para un diccionario superficial, selecciona campos o recorre dataclasses.fields().

Las dataclasses admiten herencia, aunque la composición suele ser más clara. Los métodos generados deben conciliar orden, valores predeterminados e igualdad en toda la jerarquía. Una Direccion almacenada en Cliente generalmente evoluciona mejor que un árbol de subclases sin comportamiento.

Usa dataclass con campos conocidos, igualdad útil y pocas invariantes. Usa NamedTuple cuando importe el comportamiento posicional de una tupla inmutable. Usa TypedDict cuando el valor en ejecución deba continuar siendo diccionario. Prefiere una clase común si predominan el comportamiento, el encapsulamiento o un ciclo de construcción personalizado.

Antes de activar slots=True, mide la carga y revisa la herencia. La instancia deja de tener el __dict__ normal salvo configuración específica, así que cambian los atributos dinámicos y algunas técnicas de reflexión. weakref_slot=True permite referencias débiles cuando son necesarias. El ahorro puede importar con muchos objetos pequeños, pero debe responder a una medición.

frozen=True bloquea la asignación común después de crear el objeto y ayuda a representar valores estables. No vuelve inmutables de forma recursiva los objetos internos. slots=True puede reducir memoria y evitar atributos accidentales, pero conviene usarlo por una necesidad comprobada.

Nunca definas un campo mutable como items: list[str] = []. Prefiere field(default_factory=list) para crear una lista por instancia. Reserva __post_init__ para invariantes breves; una conversión extensa de entradas externas suele pertenecer a otra capa.

Las anotaciones siguen siendo anotaciones. Combina dataclasses con type hints en Python y un verificador estático cuando el contrato importe. Una clase explícita puede comunicar mejor un objeto de dominio con mucho comportamiento.

La documentación oficial de dataclasses, consultada el 22 de julio de 2026, explica field, instancias congeladas, slots, herencia y métodos generados.