Curso de Python / Crear clases de datos con dataclass
dataclass genera métodos de inicialización, representación y comparación a partir de los campos declarados en una clase. Facilita la creación de registros de datos en Python, pero no convierte las anotaciones de tipos en validaciones automáticas ni define todas las reglas del negocio.
Cuándo utilizar una clase de datos
Un gasto con nombre, importe y etiquetas es un buen ejemplo de registro. El decorador @dataclass encuentra sus campos mediante las anotaciones y normalmente genera __init__, __repr__ y __eq__. Así evita escribir a mano operaciones repetitivas, manteniendo visible la estructura de los datos.
Una clase centrada en comportamientos complejos puede necesitar otro diseño. Tampoco basta con escribir amount: int para impedir cadenas o importes negativos en tiempo de ejecución. El programa siguiente comprueba explícitamente esa regla en __post_init__, llamado después del inicializador generado.
Ejecutar el registro con etiquetas independientes
Guarda este ejemplo completo como data_records.py y ejecútalo con python data_records.py en el entorno de prácticas. dataclasses forma parte de la biblioteca estándar; no instales un paquete externo para este ejemplo.
from dataclasses import dataclass, field
@dataclass
class Expense:
name: str
amount: int
tags: list[str] = field(default_factory=list)
def __post_init__(self):
if type(self.amount) is not int or self.amount < 0:
raise ValueError("invalid amount")
a = Expense("lunch", 8000)
b = Expense("bus", 2500)
a.tags.append("work")
print(a)
print(b.tags)
print(a == Expense("lunch", 8000, ["work"]))
Expense(name='lunch', amount=8000, tags=['work'])
[]
True
La representación muestra los nombres y valores de los campos. Añadir "work" a las etiquetas de a no modifica las de b, porque field(default_factory=list) llama a la fábrica para crear una lista nueva cuando hace falta el valor predeterminado. Esto aplica las reglas de atributos y contenedores independientes.
Separar validación inicial e inmutabilidad
La comprobación de __post_init__ acepta enteros no negativos y excluye booleanos. Sin embargo, una asignación posterior como a.amount = -1 no vuelve a ejecutar ese método. Si la regla debe mantenerse al actualizar el objeto, considera una propiedad con setter o un diseño que cree un estado nuevo en vez de modificar el existente.
frozen=True restringe la reasignación normal de campos. No impide que una lista guardada dentro de un campo reciba append. Para un registro realmente inmutable necesitas elegir también tipos anidados adecuados, como tuplas, y definir qué operaciones admite tu aplicación. Evita confundir «no reasignable» con «todo su contenido es inmutable».
Definir igualdad y conversión para el uso real
La última línea devuelve True porque los campos comparados coinciden. Eso no significa que sean el mismo objeto ni que representen la misma transacción real. Si necesitas identificar gastos de forma única, utiliza una identidad de negocio explícita y decide qué significa detectar un duplicado.
dataclasses.asdict convierte el registro en una estructura de diccionarios y realiza conversiones recursivas, pero no transforma automáticamente fechas o valores Decimal en datos aceptados por JSON. La serialización JSON necesita su propia política de representación y validación.
Prueba por separado un importe negativo y el cambio de etiquetas; restaura después los valores originales. Compara dos registros iguales y comprueba además a is b para distinguir igualdad e identidad. La referencia de dataclasses detalla las opciones del decorador y las fábricas de campos.




