Dataclasses

Intermedio
Python
Python
Actualizado: 17/04/2026

Qué son las dataclasses y por qué existen

Una dataclass es una clase cuyo propósito principal es contener datos. Antes de Python 3.7, escribir una clase así implicaba un boilerplate considerable:

class Punto:
    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __repr__(self) -> str:
        return f"Punto(x={self.x}, y={self.y})"

    def __eq__(self, otro) -> bool:
        if not isinstance(otro, Punto):
            return NotImplemented
        return self.x == otro.x and self.y == otro.y

El módulo dataclasses elimina esa repetición. Con un decorador, Python genera __init__, __repr__ y __eq__ automáticamente a partir de las anotaciones de tipo:

from dataclasses import dataclass

@dataclass
class Punto:
    x: int
    y: int

Equivalente al código anterior, mucho más claro. Los métodos generados hacen exactamente lo esperado:

p = Punto(3, 5)
print(p)                  # Punto(x=3, y=5)
p == Punto(3, 5)          # True
p == Punto(3, 6)          # False

Las dataclasses son el equivalente Python a los records de Java, las case classes de Scala o los structs de Rust: la forma moderna de declarar un tipo de datos sin escribir código repetitivo.

Campos con valores por defecto

Las dataclasses admiten valores por defecto como cualquier firma de función:

@dataclass
class Usuario:
    nombre: str
    edad: int = 0
    activo: bool = True

Los parámetros sin default deben ir antes de los que tienen default, igual que en funciones normales:

# Error: falla al declarar la clase
@dataclass
class Mal:
    activo: bool = True
    nombre: str            # error: obligatorio tras uno con default

field: control fino de cada atributo

Para casos que necesitan más configuración que un simple default, se usa field:

from dataclasses import dataclass, field

@dataclass
class Pedido:
    id: int
    items: list[str] = field(default_factory=list)
    metadatos: dict[str, str] = field(default_factory=dict)

default_factory es necesario para valores mutables por defecto (listas, diccionarios, sets). Si usaras items: list[str] = [], todas las instancias compartirían la misma lista, un bug clásico.

Otras opciones útiles de field:

  • init=False: el campo no aparece en __init__. Se inicializa en __post_init__ o por asignación posterior.
  • repr=False: el campo no aparece en __repr__ (útil para datos sensibles o voluminosos).
  • compare=False: el campo no participa en __eq__ (ni en ordenación).
  • metadata={...}: diccionario de metadatos para herramientas externas.

Ejemplo combinando opciones:

@dataclass
class Factura:
    numero: str
    total: float
    tags: list[str] = field(default_factory=list)
    _cache: dict = field(default_factory=dict, init=False, repr=False, compare=False)

post_init: lógica de inicialización

Si necesitas ejecutar código tras construir el objeto, define __post_init__:

@dataclass
class Rectangulo:
    ancho: float
    alto: float
    area: float = field(init=False)

    def __post_init__(self) -> None:
        self.area = self.ancho * self.alto

__post_init__ corre después del __init__ generado. Aquí calculamos area como valor derivado, sin requerirlo en el constructor.

También es el lugar habitual para validación:

@dataclass
class Persona:
    nombre: str
    edad: int

    def __post_init__(self) -> None:
        if self.edad < 0:
            raise ValueError("la edad no puede ser negativa")
        if not self.nombre:
            raise ValueError("el nombre no puede estar vacío")

frozen: inmutabilidad

Declarar frozen=True hace la dataclass inmutable: una vez creada, no se puede modificar. Asignar a un atributo lanza FrozenInstanceError.

@dataclass(frozen=True)
class Coordenada:
    latitud: float
    longitud: float

c = Coordenada(40.4, -3.7)
c.latitud = 41.0  # FrozenInstanceError

Además de la inmutabilidad, frozen=True habilita hashing por defecto, por lo que la instancia puede usarse como clave de diccionario o elemento de conjunto:

ciudades: dict[Coordenada, str] = {
    Coordenada(40.4, -3.7): "Madrid",
    Coordenada(41.4, 2.2): "Barcelona",
}

La inmutabilidad es recomendable por defecto para datos que representan valores del dominio, igual que strings o tuplas. El código se vuelve más predecible y seguro en concurrencia.

slots: ahorro de memoria

Con slots=True (disponible desde Python 3.10), la dataclass no usa el diccionario habitual de instancia sino un array fijo de atributos. Esto ahorra memoria y acelera el acceso.

@dataclass(slots=True)
class Punto:
    x: int
    y: int

La diferencia es notable en estructuras de datos grandes: un millón de instancias de Punto con slots=True ocupa del orden de la mitad de memoria que sin slots.

Limitación: no se pueden añadir atributos dinámicos en tiempo de ejecución (p.nuevo = 1 falla), lo que de hecho suele ser una ventaja porque captura typos.

order: dataclasses ordenables

Con order=True, Python genera también __lt__, __le__, __gt__, __ge__ basándose en una comparación por tuplas de los atributos:

@dataclass(order=True)
class Tarea:
    prioridad: int
    descripcion: str

tareas = [Tarea(3, "tarde"), Tarea(1, "urgente"), Tarea(2, "normal")]
tareas.sort()
# [Tarea(prioridad=1, ...), Tarea(prioridad=2, ...), Tarea(prioridad=3, ...)]

Si quieres controlar qué campos se usan para ordenar, combina con field(compare=False) para excluir algunos, o define el orden explícitamente con un sort key.

Comparación con namedtuple y TypedDict

Python tiene tres formas principales de representar datos:

| Herramienta | Inmutable | Mutable | Hereda métodos | Type hints | |-------------|:---------:|:-------:|:--------------:|:----------:| | namedtuple | Sí | No | Limitado | No nativo | | TypedDict | Sí (dict) | Sí (dict) | No | Sí | | dataclass | Opcional | Sí por defecto | Sí | Sí |

En 2026, dataclass es la opción por defecto en la mayoría de casos. TypedDict se usa cuando la estructura proviene de JSON o de una API y queremos tratarla como dict. namedtuple sobrevive por compatibilidad pero dataclass con frozen=True y slots=True la cubre mejor.

Conversión a dict o tupla

El módulo ofrece utilidades para serializar:

from dataclasses import asdict, astuple

@dataclass
class Usuario:
    nombre: str
    edad: int

u = Usuario("Ana", 30)
asdict(u)    # {"nombre": "Ana", "edad": 30}
astuple(u)   # ("Ana", 30)

asdict es recursivo: si un campo es una dataclass anidada, también se convierte. Muy útil para generar JSON o payloads de API.

Cuándo usar pydantic en lugar de dataclass

Las dataclasses son estructurales: definen la forma de los datos sin validar nada. Si trabajas en los bordes de la aplicación (entrada HTTP, lectura de JSON, formularios) y necesitas validación en tiempo de ejecución, la opción moderna es pydantic v2.

Como regla simple:

  • Dataclass: datos ya validados dentro del dominio.
  • Pydantic: datos que llegan desde fuera y hay que validar.

Ambas conviven bien: pydantic se usa en las APIs y los bordes; dataclasses en el núcleo de dominio donde los invariantes ya están asegurados.

Recapitulación

Las dataclasses reducen el boilerplate de las clases de datos y hacen el código más claro. Con frozen=True obtienes inmutabilidad, con slots=True ahorras memoria, con order=True las haces ordenables. Para la mayoría de clases cuyo propósito principal es contener datos, la opción correcta por defecto en Python moderno es una dataclass bien configurada.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Python es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Python

Explora más contenido relacionado con Python y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Declarar clases de datos con el decorador dataclass. Diferenciar campos obligatorios, con default y con factory. Usar field para configuraciones avanzadas y slots para ahorrar memoria. Hacer dataclasses inmutables con frozen y congeladas ordenables con order.