Tu país

Las herramientas que lo admiten usan tu país para la moneda local, el formato de los números, las unidades y el tamaño del papel. Tu elección se guarda solo en este navegador.

Escribe un nombre o un código de dos letras. Usa las flechas arriba y abajo para recorrer los países, Intro para elegir uno y Escape para cerrar.

Python  Módulo 9 – Python orientado a objetos y el modelo de datos

Dataclasses para objetos de datos limpios

Escribe dataclasses de Python con valores por defecto y fábricas, valida en __post_init__, usa frozen, order, slots y kw_only, y compáralas con pydantic.

Base gratis, profundidad Pro: 8 de 20 minutos gratis
Gratis: El decorador escribe tres métodos a partir de los campos anotados · Un valor por defecto de lista se rechaza, y una fábrica da a cada objeto el suyo · Cuatro opciones cambian lo que escribe el decorador · 2 secciones más. Con un pase: 2 partes en profundidad y 3 respuestas de entrevista, unos 12 minutos.

Qué vas a aprender

  • Definir dataclasses con valores por defecto, fábricas por defecto y valores calculados
  • Usar las opciones frozen, order, slots y kw_only y predecir lo que escribe cada una
  • Validar y derivar campos en __post_init__, y copiar objetos con replace()
  • Elegir entre una dataclass, una NamedTuple, attrs y pydantic para una tarea
  • Reconocer las trampas de las dataclasses en la igualdad, el hash, el orden de los campos y las copias superficiales

Antes de empezar

Qué vas a aprender · Antes de empezar

Qué vas a aprender

  • Definir dataclasses con valores por defecto, fábricas por defecto y valores calculados
  • Usar las opciones frozen, order, slots y kw_only y predecir lo que escribe cada una
  • Validar y derivar campos en __post_init__, y copiar objetos con replace()
  • Elegir entre una dataclass, una NamedTuple, attrs y pydantic para una tarea
  • Reconocer las trampas de las dataclasses en la igualdad, el hash, el orden de los campos y las copias superficiales

Antes de empezar

En esta página

Un artículo de un pedido con tres campos necesita tres métodos escritos a mano antes de ser útil: un __init__ que guarda los campos, un __repr__ que los muestra y un __eq__ que los compara. Una dataclass: Una clase decorada con @dataclass. El decorador escribe su __init__, su __repr__ y su __eq__ a partir de los campos anotados en su cuerpo, como name: str y quantity: int = 1, y opciones como frozen=True u order=True añaden más. es una clase normal cuyos métodos aburridos escribe Python por ti. Pon @dataclass encima de una clase cuyo cuerpo enumera sus campo de una dataclass: Una línea con una anotación de tipo en el cuerpo de una dataclass, como quantity: int = 1. Su nombre pasa a ser un parámetro del __init__ escrito, con su valor por defecto si lo tiene; field() añade opciones como default_factory o compare=False. con anotaciones de tipo, como name: str y quantity: int = 1, y el decorador de clase: Un decorador escrito encima de una sentencia class. Recibe la clase cuando ya se ha ejecutado el cuerpo de la clase, y el nombre de la clase se enlaza a lo que devuelve, normalmente la misma clase, cambiada. Más en “Decoradores: envolver funciones con limpieza” (en inglés) escribe los tres a partir de ellos.

Las opciones cambian lo que escribe: frozen=True hace que los objetos sean inmutables y hashables, order=True añade <, <=, > y >=, slots=True añade __slots__: Un atributo de clase que enumera los nombres de los atributos de instancia, como __slots__ = ("x", "y"). Los objetos de la clase reciben un sitio fijo por nombre y ningún __dict__, así que ocupan menos memoria y rechazan cualquier otro nombre de atributo. Más en “__slots__, referencias débiles y ciclo de vida de los objetos” (en inglés) y kw_only=True convierte cada campo en un argumento con nombre. Un valor por defecto mutable, como una lista, necesita field(default_factory=list), y __post_init__ se ejecuta después de __init__ para validar o derivar valores. Las dataclasses van bien para los registros que son sobre todo datos, como pedidos, reservas, ajustes y mensajes, mientras que las clases con un comportamiento rico siguen mereciendo métodos escritos a mano. A continuación construyes un pedido con sus artículos, te encuentras con el error del valor por defecto mutable y pruebas todas las opciones.

El decorador escribe tres métodos a partir de los campos anotados

Un pedido y sus artículos, con un total calculado Python · order_items.py
import inspect
from dataclasses import dataclass, field, fields


@dataclass
class OrderItem:
    name: str
    unit_paise: int
    quantity: int = 1

    @property
    def total_paise(self):
        return self.unit_paise * self.quantity


@dataclass
class Order:
    number: int
    items: list[OrderItem] = field(default_factory=list)

    def total_paise(self):
        return sum(item.total_paise for item in self.items)


order = Order(1042, [OrderItem("masala dosa", 9000, 2), OrderItem("filter coffee", 4000)])
print(order)
print("total:", order.total_paise(), "| equal to a copy:", order == Order(1042, list(order.items)))
print("generated __init__:", inspect.signature(OrderItem))
print("fields:", [(f.name, f.type) for f in fields(OrderItem)])

Salida

Order(number=1042, items=[OrderItem(name='masala dosa', unit_paise=9000, quantity=2), OrderItem(name='filter coffee', unit_paise=4000, quantity=1)])
total: 22000 | equal to a copy: True
generated __init__: (name: str, unit_paise: int, quantity: int = 1) -> None
fields: [('name', <class 'str'>), ('unit_paise', <class 'int'>), ('quantity', <class 'int'>)]

Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 order_items.py

@dataclass convierte los campos anotados en __init__, __repr__ y __eq__; opciones como order y frozen añaden más.class OrderItem: name: str unit_paise: int quantity: int = 1@dataclass lee las anotacionesen orden: name, unit_paise, quantity__init__(self, name, unit_paise, quantity=1)__repr__: OrderItem(name=…, unit_paise=…, quantity=…)__eq__: misma clase y campos igualeslas opciones cambian lo que se escribeorder=True: <, <=, >, >=frozen=True: sin asignación, y __hash__slots=True: __slots__kw_only=True: __init__ solo por nombrey con opciones

Lo que escribe @dataclass a partir del cuerpo de una clase

Descripción en texto del diagrama

El diagrama es un flujo de arriba abajo.

  • Arriba hay un cuerpo de clase con tres campos anotados: name de tipo str, unit_paise de tipo int y quantity de tipo int con el valor por defecto 1.
  • El decorador @dataclass lee las anotaciones en orden.
  • Escribe tres métodos: __init__, con un parámetro por campo y el valor por defecto de quantity; __repr__, que muestra cada campo por su nombre; y __eq__, que compara campo a campo dos objetos de la misma clase.
  • Las opciones cambian lo que se escribe: order=True añade las cuatro comparaciones de orden, frozen=True rechaza la asignación y añade __hash__, slots=True añade __slots__ y kw_only=True hace que los parámetros de __init__ sean solo por nombre.
  • Los campos salen de las anotaciones, en orden, como muestra el diagrama, y quantity: int = 1 pasó a ser un valor por defecto de __init__, como muestra la firma generada.
  • El repr nombra cada campo, incluidas las dataclasses anidadas, lo que hace legibles los registros y los fallos de las pruebas.
  • Dos pedidos con campos iguales son iguales. __eq__ compara campo a campo, y solo objetos de la misma clase.
  • Los métodos y las propiedades se escriben como en cualquier clase: total_paise es una propiedad de un artículo y total_paise() un método del pedido. fields() enumera los campos con sus tipos, para el código que trabaja con cualquier dataclass.

Recorre las llamadas que hace el programa a los métodos escritos:

Interactivo · mensaje a mensaje Los métodos escritos de OrderItem y de Order en acción Recorre order_items.py. Cada llamada de la derecha es un método que el decorador escribió a partir de las anotaciones.
Los métodos escritos de OrderItem y de Order en acción, paso 6 de 6. Mensajes hasta ahora: de Tu código a Los métodos escritos: OrderItem(…, 9000, 2); de Los métodos escritos a Los objetos nuevos: name, unit_paise, quantity; de Tu código a Los métodos escritos: OrderItem(…, 4000); de Tu código a Los métodos escritos: print(order); de Tu código a Los métodos escritos: order == Order(1042, …).Tu códigoorder_items.pyLos métodos escritos__eq__: TrueLos objetos nuevosfilter coffee × 1 añadidoOrderItem(…, 9000, 2)name, unit_paise, quantityOrderItem(…, 4000)print(order)order == Order(1042, …)

El dibujo muestra la escena completa; sus 6 pasos aparecen abajo.

Todos los pasos en texto
  1. El primer artículo. OrderItem("masala dosa", 9000, 2) invoca el __init__ escrito, cuyos parámetros son los campos en el orden de las anotaciones: name, unit_paise, quantity.
  2. Campos guardados. __init__ asigna cada argumento a su campo, así que el artículo contiene name, unit_paise y quantity, y su propiedad total_paise da 18000.
  3. Un valor por defecto. OrderItem("filter coffee", 4000) deja fuera quantity, así que __init__ usa el valor por defecto del cuerpo de la clase, 1.
  4. El repr. print(order) invoca el __repr__ escrito, que muestra cada campo por su nombre, incluidos los artículos que contiene, porque una lista muestra el repr de cada elemento.
  5. Iguales por sus campos. order == Order(1042, list(order.items)) invoca el __eq__ escrito. Los dos son Order, los números son iguales y las dos listas contienen los mismos artículos, así que la respuesta es True.
  6. La firma. inspect.signature(OrderItem) muestra el __init__ que se escribió, (name, unit_paise, quantity = 1), con los tipos copiados de las anotaciones; nada los comprueba cuando se ejecuta el programa.

Las anotaciones no se comprueban cuando se ejecuta el programa: OrderItem("tea", "forty") se acepta, porque los tipos son para los lectores y para los comprobadores de tipos. Las comprobaciones que tienen que ejecutarse van en __post_init__.

Punto de control

Comprueba: lo que escribe @dataclass

  1. Pregunta 1 de 2 ¿Qué __init__ escribe @dataclass para OrderItem, cuyo cuerpo es name: str, unit_paise: int y quantity: int = 1?

    Elige una respuesta.

    Ver la respuesta: pregunta 1 de 2

    Respuesta: (self, name, unit_paise, quantity=1)

    El programa imprime la firma (name: str, unit_paise: int, quantity: int = 1) -> None: el decorador convirtió las anotaciones en los parámetros de __init__.

  2. Pregunta 2 de 2 order == Order(1042, list(order.items)) es True. ¿Qué compara el __eq__ escrito?

    Elige una respuesta.

    Ver la respuesta: pregunta 2 de 2

    Respuesta: Los campos en orden, primero number y después items, de dos objetos de la misma clase

    list(order.items) es una lista nueva con los mismos objetos OrderItem, así que las listas son iguales y también los pedidos, aunque son dos objetos distintos.

Un valor por defecto de lista se rechaza, y una fábrica da a cada objeto el suyo

Un valor por defecto de lista rechazado, una fábrica aceptada, y el orden de los campos Python · defaults.py
from dataclasses import dataclass, field

try:
    @dataclass
    class Cart:
        items: list = []  # one list for every cart, so dataclasses refuses it
except ValueError as error:
    print("ValueError:", error)


@dataclass
class Cart:
    items: list = field(default_factory=list)  # a new list for every cart


first, second = Cart(), Cart()
first.items.append("tea")
print(first, second, first.items is second.items)

try:
    @dataclass
    class Ticket:
        coach: str = "S1"
        seat: int  # a field without a default after one with a default
except TypeError as error:
    print("TypeError:", error)

Salida

ValueError: mutable default <class 'list'> for field items is not allowed: use default_factory
Cart(items=['tea']) Cart(items=[]) False
TypeError: non-default argument 'seat' follows default argument 'coach'

Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 defaults.py

Un valor por defecto se evalúa una vez, cuando se define la clase, así que una lista escrita como valor por defecto sería una sola lista compartida por todos los carritos. Las dataclasses la rechazan en el acto con ValueError y nombran el arreglo: field(default_factory=list) invoca list() para cada objeto nuevo, así que los dos carritos tienen listas separadas. Recorre los carritos y las listas que contienen:

Interactivo · paso a paso Una lista para todos los carritos, o una lista para cada uno Recorre defaults.py. A la izquierda los carritos, a la derecha las listas a las que se refiere su campo items.
Una lista para todos los carritos, o una lista para cada uno, paso 5 de 5. Se ve: first = Cart() (items: una lista nueva), La lista de first (['tea']), second = Cart() (items: otra lista nueva), La lista de second.first = Cart()items: una lista nuevaLa lista de first['tea']second = Cart()items: otra lista nuevaLa lista de second[]Ticket: TypeError en seat

El dibujo muestra la escena completa; sus 5 pasos aparecen abajo.

Todos los pasos en texto
  1. Una lista como valor por defecto. Un valor por defecto se evalúa una vez, cuando se define la clase, así que items = [] sería una sola lista compartida por todos los carritos. El decorador lo rechaza con ValueError y nombra el arreglo, default_factory.
  2. Una fábrica en su lugar. field(default_factory=list) guarda la función list. El __init__ escrito la invoca para cada carrito nuevo, así que first recibe una lista propia.
  3. Un segundo carrito. second = Cart() vuelve a llamar a list(), así que su campo items se refiere a una lista distinta.
  4. Añadir a uno. first.items.append("tea") cambia solo la lista de first, así que el programa imprime Cart(items=['tea']) y Cart(items=[]), y first.items is second.items es False.
  5. El orden de los campos. Los campos pasan a ser los parámetros de __init__, así que un campo sin valor por defecto después de uno con valor por defecto, seat después de coach = "S1", falla con TypeError, como fallaría en cualquier def.

Desde Python 3.11 el decorador rechaza cualquier valor por defecto no hashable, diccionarios y conjuntos incluidos, porque un valor no hashable probablemente es mutable. Un valor por defecto hashable se sigue evaluando una sola vez, así que un valor que tenga que ser nuevo para cada objeto viene de una La función que guarda field(default_factory=…). El __init__ escrito la invoca sin argumentos para cada objeto nuevo, así que cada objeto recibe un valor nuevo, como una lista vacía propia.. El orden de los campos también importa, porque los campos pasan a ser los parámetros de __init__: un campo sin valor por defecto no puede ir después de uno con valor por defecto, como en cualquier función de Python.

Punto de control

Comprueba: valores por defecto y fábricas por defecto

  1. Pregunta 1 de 2 ¿Por qué rechaza @dataclass el items: list = []?

    Elige una respuesta.

    Ver la respuesta: pregunta 1 de 2

    Respuesta: El valor por defecto se evalúa una vez, así que todos los objetos compartirían esa única lista

    El mensaje de error nombra el arreglo: mutable default <class 'list'> for field items is not allowed: use default_factory.

  2. Pregunta 2 de 2 ¿Qué pasa con @dataclass class Ticket:, cuyos campos son seat: int = 0 y después coach: str?

    Elige una respuesta.

    Ver la respuesta: pregunta 2 de 2

    Respuesta: TypeError: non-default argument 'coach' follows default argument 'seat'

    La misma regla que en cualquier def: pon primero los campos sin valor por defecto, o haz que los posteriores sean solo por nombre.

Cuatro opciones cambian lo que escribe el decorador

Un punto congelado como clave, versiones que se pueden ordenar y ajustes solo por nombre Python · options.py
from dataclasses import FrozenInstanceError, dataclass


@dataclass(frozen=True)
class Point:
    x: int
    y: int


home = Point(12, 7)
visits = {home: 3}
print(visits[Point(12, 7)], hash(home) == hash(Point(12, 7)))
try:
    home.x = 13
except FrozenInstanceError as error:
    print("FrozenInstanceError:", error)


@dataclass(order=True)
class Release:
    major: int
    minor: int
    patch: int = 0


print(sorted([Release(3, 14, 8), Release(3, 9), Release(3, 14)]), Release(3, 9) < Release(3, 14))


@dataclass(kw_only=True, slots=True)
class Retry:
    attempts: int = 3
    delay_seconds: float = 0.5


print(Retry(delay_seconds=2.0), hasattr(Retry(), "__dict__"))
try:
    Retry(5)
except TypeError as error:
    print("TypeError:", error)

Salida

3 True
FrozenInstanceError: cannot assign to field 'x'
[Release(major=3, minor=9, patch=0), Release(major=3, minor=14, patch=0), Release(major=3, minor=14, patch=8)] True
Retry(attempts=3, delay_seconds=2.0) False
TypeError: Retry.__init__() takes 1 positional argument but 2 were given

Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 options.py

frozen=True rechaza la asignación después de la creación, con FrozenInstanceError, y como los campos ya no pueden cambiar, el decorador también escribe un __hash__ a partir de ellos: un punto dataclass congelada: Una dataclass declarada con frozen=True. Asignar a un campo después de crear el objeto lanza FrozenInstanceError, y con el eq=True por defecto el decorador también escribe un __hash__ a partir de los campos, así que los objetos pueden ser claves de un dict. sirve como clave de un diccionario. Una dataclass normal no, porque, como cualquier clase que define __eq__ sin __hash__, no es hashable. order=True compara los objetos campo a campo, como las tuplas, así que las versiones se ordenan por major, después por minor y después por patch. Sigue las comparaciones de las versiones:

Interactivo · etapa por etapa Cómo compara order=True dos versiones Recorre las comparaciones que hacen sorted() y < en options.py. Un campo solo decide cuando todos los campos anteriores son iguales.
Cómo compara order=True dos versiones, paso 4 de 4. Se ve: Comparar major (3 = 3: seguir), Comparar minor (14 = 14: seguir), Comparar patch (0 < 8: decided), La respuesta (3.9.0, 3.14.0, 3.14.8). Resaltado: La respuesta.Comparar major3 = 3: seguirComparar minor14 = 14: seguirComparar patch0 < 8: decidedLa respuesta3.9.0, 3.14.0, 3.14.8

El dibujo muestra la escena completa; sus 4 pasos aparecen abajo.

Todos los pasos en texto
  1. Release(3, 9) < Release(3, 14). El __lt__ escrito compara los primeros campos. Los dos major son 3, así que la comparación sigue con el campo siguiente.
  2. Decide el minor. 9 es menor que 14, así que la respuesta es True y el patch ni se mira, como con las tuplas (3, 9, 0) y (3, 14, 0).
  3. 3.14.0 frente a 3.14.8. Para sorted(), los major son iguales y también los minor, 14 y 14, así que decide el patch.
  4. La lista ordenada. sorted() puso las tres versiones en el orden 3.9.0, 3.14.0, 3.14.8, porque cada comparación toma los campos en el orden en que se declaran.

kw_only=True hace que cada parámetro sea parámetro solo por nombre: Un parámetro después de * o *args en una firma, como key en sorted(iterable, /, *, key=None, reverse=False). Una llamada tiene que pasarlo por su nombre. Más en “Parámetros: por defecto, solo por nombre y solo por posición” (en inglés), lo que mantiene legibles las llamadas cuando una clase tiene varios ajustes del mismo tipo, y slots=True da a la clase __slots__, así que sus objetos no tienen __dict__ y usan menos memoria.

Los campos sueltos también tienen opciones. field(repr=False) deja un valor fuera del repr, lo que importa con las contraseñas y los tokens que, si no, acabarían en los registros y en los informes de errores, y field(compare=False) deja un campo fuera de la igualdad. Las dataclasses también funcionan en las sentencias match: el decorador escribe __match_args__: Un atributo de clase que contiene una tupla de nombres de atributos. Le dice a un patrón de clase qué atributo representa cada subpatrón posicional; @dataclass lo fija a los campos en el orden en que se declaran. Más en “Coincidencia estructural: listas, diccionarios y clases” (en inglés), así que case OrderItem(name, price, 1): encaja un artículo por posición. Las versiones que trajeron estas opciones:

Cronología · 3 momentos en orden Cuándo llegaron las opciones de @dataclass Recorre las versiones en orden. Un proyecto que admite un Python más antiguo solo puede usar las opciones de su versión.
  1. 3.7

    El módulo dataclasses

    La PEP 557 añadió @dataclass y field(), con las opciones init, repr, eq, order y frozen.

  2. 3.10

    slots, kw_only y match_args opciones nuevas

    slots=True añade __slots__ a la clase, kw_only=True hace que todos los parámetros sean solo por nombre, y match_args escribe __match_args__ para las sentencias match.

  3. 3.11

    Valores por defecto no hashables rechazados cambiado

    En lugar de rechazar solo list, dict y set, el decorador rechaza ahora cualquier valor por defecto no hashable, porque un valor no hashable probablemente es mutable.

La cronología tiene 3 momentos, de 3.7 a 3.11.

Punto de control

Comprueba: frozen, order, slots y kw_only

  1. Pregunta 1 de 2 home = Point(12, 7) es un objeto @dataclass(frozen=True). ¿Qué hace home.x = 13?

    Elige una respuesta.

    Ver la respuesta: pregunta 1 de 2

    Respuesta: Lanza FrozenInstanceError

    Como los campos ya no pueden cambiar, el decorador también escribe un __hash__ a partir de ellos, y por eso un punto congelado sirve como clave de un diccionario.

  2. Pregunta 2 de 2 Con order=True, ¿qué da sorted([Release(3, 9), Release(2, 30), Release(3, 1, 5)]), escrito como versiones?

    Elige una respuesta.

    Ver la respuesta: pregunta 2 de 2

    Respuesta: 2.30.0, 3.1.5, 3.9.0

    order=True compara los campos en el orden en que se declaran, como las tuplas (2, 30, 0), (3, 1, 5) y (3, 9, 0).

dataclass, NamedTuple, attrs o pydantic: ¿cuál encaja?

Tres preguntas sobre tus datos deciden casi toda la elección; respóndelas en la tabla:

Interactivo · responde y compara ¿Qué clase de datos encaja con tu registro? Responde tres preguntas sobre tus datos. Cada respuesta descarta algunas opciones o añade una advertencia, con la razón.
¿De dónde vienen los datos?
¿Tiene que cambiar un registro después de crearse?
¿Puede el proyecto añadir un paquete de terceros?

Responde las preguntas de arriba: entonces la tabla marca lo que encaja con tu caso y dice por qué el resto no.

Cuatro formas de escribir una clase de datos, frente a cuatro cosas que puede necesitar un registro
Herramienta Comprueba los valores al crearse Puede cambiar después de crearse Se desempaqueta como una tupla Nada que instalar
@dataclass Viene de: la biblioteca estándar Regular solo lo que escribas en __post_init__; las anotaciones no se comprueban Bien sí, salvo con frozen=True Malo no, no es iterable Bien viene con Python
typing.NamedTuple (en inglés) Viene de: la biblioteca estándar Malo no, las anotaciones no se comprueban Malo no, es tan inmutable como cualquier tupla Bien sí: p.x y p[0] leen el mismo campo Bien viene con Python
attrs Viene de: un paquete de terceros Regular con los validadores y conversores que añadas Bien sí, salvo que esté congelado Malo no, no es una tupla Malo un paquete que instalar y mantener al día
pydantic Viene de: un paquete de terceros Bien sí, y convierte: el texto "34" pasa a ser el int 34 Bien sí, por defecto Malo no, no es una tupla Malo un paquete que instalar y mantener al día
Cómo deciden las respuestas
  • “¿De dónde vienen los datos?” De fuera: JSON, formularios, archivos de ajustes: descarta typing.NamedTuple y añade una advertencia a @dataclass y a attrs. Los datos de fuera hay que comprobarlos y convertirlos cuando se crea el objeto. Una dataclass solo comprueba lo que escribas en __post_init__, attrs lo que comprueben sus validadores.
  • “¿De dónde vienen los datos?” Los crea mi propio código: añade una advertencia a pydantic. Ya se sabe que los datos que crea tu propio código son válidos, así que comprobarlos otra vez en cada creación cuesta tiempo para nada.
  • “¿Tiene que cambiar un registro después de crearse?” Sí, sus campos se actualizan: descarta typing.NamedTuple. Una NamedTuple es inmutable, así que un registro cuyos campos se actualizan necesita otro tipo.
  • “¿Puede el proyecto añadir un paquete de terceros?” No, solo la biblioteca estándar: descarta attrs y pydantic. Los dos son paquetes de terceros; la biblioteca estándar ofrece @dataclass y NamedTuple.

Una dataclass es la elección por defecto para los datos que crea tu propio código. Una tupla con nombre: Un tipo de tupla cuyas posiciones también tienen nombre, creado con typing.NamedTuple o collections.namedtuple(). p.x y p[0] leen el mismo campo, y es tan inmutable como cualquier tupla. Más en “Tuplas, desempaquetado y tuplas con nombre” (en inglés) es inmutable y se desempaqueta como una tupla, lo que va bien para valores pequeños como las coordenadas. attrs es la biblioteca de terceros que inspiró las dataclasses, con validadores, conversores y algunas opciones más. pydantic comprueba y convierte la entrada cuando se crea un objeto, que es lo que necesitan los datos de fuera.

La elección rara vez es de todo o nada. Muchos programas usan dataclasses normales por dentro, donde ya se sabe que los datos son válidos, y un modelo que valida solo en los bordes, donde llegan las peticiones, los archivos y los mensajes; asdict() convierte una dataclass en un diccionario que acepta un modelo así, o json.dumps.

Punto de control

Comprueba: dataclass, NamedTuple, attrs o pydantic

  1. Pregunta 1 de 2 Un programa pasa de un lado a otro coordenadas pequeñas y fijas y las desempaqueta con x, y = p. ¿Cuál de las cuatro encaja mejor?

    Elige una respuesta.

    Ver la respuesta: pregunta 1 de 2

    Respuesta: typing.NamedTuple

    Los campos de una NamedTuple tienen nombres, p.x, y posiciones, p[0], y es tan inmutable como cualquier tupla.

  2. Pregunta 2 de 2 ¿Qué hace OrderItem("tea", "forty"), con unit_paise: int?

    Elige una respuesta.

    Ver la respuesta: pregunta 2 de 2

    Respuesta: Crea el artículo; la anotación no se comprueba cuando se ejecuta el programa

    Un comprobador de tipos marcaría la llamada antes de que se ejecute el programa; en tiempo de ejecución, la dataclass guarda lo que reciba.

Generador de código a partir de JSON (en inglés) Pega una respuesta JSON y obtén las dataclasses de Python para ella, y después compáralas con lo que escribirías a mano.

Ponte a prueba

Práctica que se adapta a ti

Practica esta lección

12 preguntas sobre 10 ideas de esta lección. Cada respuesta se explica al momento, y lo que aciertas se guarda en este navegador.

  1. Pregunta 1 de 12 Una clase está decorada con un @dataclass sin más. ¿Qué métodos escribe el decorador para ella?

    Elige todas las respuestas correctas.

    Ver la respuesta: pregunta 1 de 12

    Respuesta:

    • __eq__
    • __repr__
    • __init__

    Por defecto, una dataclass recibe __init__, __repr__ y __eq__. El orden necesita order=True, y como __eq__ se escribe sin frozen=True, __hash__ se fija a None.

  2. Pregunta 2 de 12 ¿Qué imprime print(Order(7, [OrderItem("tea", 1000)]))?

    Elige una respuesta.

    Ver la respuesta: pregunta 2 de 12

    Respuesta: Order(number=7, items=[OrderItem(name='tea', unit_paise=1000, quantity=1)])

    Las dataclasses anidadas conservan su propio repr escrito, que es lo que hace legibles los registros y los fallos de las pruebas.

  3. Pregunta 3 de 12 ¿Qué clase gana menos con @dataclass?

    Elige una respuesta.

    Ver la respuesta: pregunta 3 de 12

    Respuesta: Una clase cuyos métodos imponen reglas, como una cuenta que solo puede cambiar mediante deposit() y withdraw()

    Las dataclasses van bien para los registros que son sobre todo datos, como pedidos, reservas, ajustes y mensajes; las clases con un comportamiento rico siguen mereciendo métodos escritos a mano.

  4. Pregunta 4 de 12 ¿Cómo se le da a un campo de una dataclass una lista vacía como valor por defecto?

    Elige una respuesta.

    Ver la respuesta: pregunta 4 de 12

    Respuesta: items: list = field(default_factory=list)

    default_factory se invoca para cada objeto nuevo, así que cada uno recibe su propia lista. Un valor por defecto [] sin más lo compartirían todos los objetos, y por eso las dataclasses lo rechazan con ValueError; field(default=[]) se rechaza por la misma razón.

  5. Pregunta 5 de 12 ¿Qué imprime defaults.py?

    ¿Qué imprime este programa? Elige una respuesta.

    from dataclasses import dataclass, field
    
    try:
        @dataclass
        class Cart:
            items: list = []  # one list for every cart, so dataclasses refuses it
    except ValueError as error:
        print("ValueError:", error)
    
    
    @dataclass
    class Cart:
        items: list = field(default_factory=list)  # a new list for every cart
    
    
    first, second = Cart(), Cart()
    first.items.append("tea")
    print(first, second, first.items is second.items)
    
    try:
        @dataclass
        class Ticket:
            coach: str = "S1"
            seat: int  # a field without a default after one with a default
    except TypeError as error:
        print("TypeError:", error)
    Ver la respuesta: pregunta 5 de 12

    Respuesta: imprime

    ValueError: mutable default <class 'list'> for field items is not allowed: use default_factory
    Cart(items=['tea']) Cart(items=[]) False
    TypeError: non-default argument 'seat' follows default argument 'coach'

    El valor por defecto de lista sin más se rechaza cuando se define la clase. Con default_factory, cada carrito recibe una lista propia. Un campo sin valor por defecto no puede ir después de uno con valor por defecto, porque el __init__ generado tendría un parámetro obligatorio después de uno opcional.

  6. Pregunta 6 de 12 Un campo se escribe created: datetime = datetime.now(). ¿Qué sale mal?

    Elige una respuesta.

    Ver la respuesta: pregunta 6 de 12

    Respuesta: Todos los objetos reciben la hora en que se definió la clase; usa field(default_factory=datetime.now)

    La regla que hay detrás del error de la lista vale para cualquier valor por defecto: se evalúa una vez. Cuando cada objeto necesite un valor nuevo, pasa una función a default_factory.

  7. Pregunta 7 de 12 ¿Por qué un objeto @dataclass(frozen=True) puede ser clave de un diccionario y un objeto de una dataclass normal no?

    Elige una respuesta.

    Ver la respuesta: pregunta 7 de 12

    Respuesta: Con frozen=True y el eq=True por defecto, el decorador también escribe un __hash__ a partir de los campos

    Una dataclass normal tiene un __eq__ a partir de sus campos y __hash__ fijado a None, así que no es hashable. Congelarla promete que los campos nunca cambian, así que un hash construido a partir de ellos sigue siendo válido.

  8. Pregunta 8 de 12 ¿Qué opción hace que el decorador escriba <, <=, > y >=?

    Elige una respuesta.

    Ver la respuesta: pregunta 8 de 12

    Respuesta: order=True

    sorted() y min() necesitan <, que order=True escribe a partir de los campos en su orden de declaración.

  9. Pregunta 9 de 12 Settings tiene seis campos int con valores por defecto, y llamadas como Settings(3, 5, 0, 1, 2, 9) son difíciles de leer y fáciles de confundir. ¿Qué opción ayuda?

    Elige una respuesta.

    Ver la respuesta: pregunta 9 de 12

    Respuesta: kw_only=True, para que cada valor tenga que pasarse por su nombre

    kw_only=True mantiene legibles las llamadas cuando una clase tiene varios ajustes del mismo tipo, como muestra Retry con delay_seconds=2.0.

  10. Pregunta 10 de 12 Los datos llegan como JSON desde un formulario web y hay que comprobarlos y convertirlos, por ejemplo "34" en el int 34. ¿Qué herramienta encaja mejor?

    Elige una respuesta.

    Ver la respuesta: pregunta 10 de 12

    Respuesta: pydantic, que valida y convierte la entrada cuando se crea el objeto

    Las anotaciones de una dataclass no se comprueban cuando se ejecuta el programa; documentan los tipos y ayudan a los comprobadores de tipos. Comprobar y convertir la entrada de fuera es justo para lo que está hecho pydantic.

  11. Pregunta 11 de 12 ¿Cuáles de las cuatro vienen con Python, sin nada que instalar?

    Elige todas las respuestas correctas.

    Ver la respuesta: pregunta 11 de 12

    Respuesta:

    • @dataclass
    • typing.NamedTuple (en inglés)

    Dos vienen con Python y dos son paquetes que hay que instalar, lo que importa en un proyecto que solo puede usar la biblioteca estándar.

  12. Pregunta 12 de 12 Un servicio lee peticiones JSON, las comprueba y pasa los datos a muchas funciones internas. ¿Qué diseño sigue la lección?

    Elige una respuesta.

    Ver la respuesta: pregunta 12 de 12

    Respuesta: Un modelo que valida en el borde para las peticiones, y dataclasses normales por dentro

    Muchos programas usan dataclasses normales por dentro, donde ya se sabe que los datos son válidos, y un modelo que valida solo en los bordes; asdict() convierte una dataclass en un diccionario que acepta un modelo así o json.dumps.

Validación, copias e igualdad: __post_init__, replace() y las trampas, reproducidas Pro: se abre con un pase Pro, Premium o Ultimate

Los objetos de datos reales necesitan reglas: un nombre no puede estar vacío, una edad tiene que ser posible, un total se calcula, no se escribe a mano. Esta parte valida y deriva campos en __post_init__, copia objetos sin compartir sus listas y reproduce las reglas de igualdad que sorprenden a la gente.

En esta parte:

  • Validar y derivar campos al crear cada objeto
  • Copias: replace() es superficial, asdict() es profunda
  • Una igualdad que compara menos, o nada

2 diagramas · 3 ejemplos ejecutables (Python) · unos 8 minutos

Abriendo esta parte con tu pase…

Esta parte se abre con cualquier pase de MySmartCoPilot.

  • Pase Pro · 1 semana79 ₹2,99 US$
  • 30 días179 ₹5,99 US$
  • 1 año1699 ₹45 US$

Un solo pago; un pase nunca se renueva. El precio que se muestra es el total que pagas: al pagar no se añaden comisiones ni cargos.

También abre todas las demás partes en profundidad de Learn y todas las herramientas Pro. Las ventas son definitivas, salvo cuando la ley te da un derecho de desistimiento, por ejemplo en la UE y el Reino Unido (política de reembolso (en inglés)).

Objetos congelados, herencia y memoria a fondo, medidos, y un ejercicio Pro: se abre con un pase Pro, Premium o Ultimate

Las opciones interactúan: congelar cambia el hash y __post_init__, la herencia cambia el orden de los campos, y los slots cambian la memoria. Esta parte trabaja cada interacción, mide la memoria de cuatro tipos de registro y termina con una reserva de tren construida con dataclasses.

En esta parte:

  • Reglas del hash, y campos derivados en una clase congelada
  • Herencia y orden de los campos
  • Memoria por registro, medida
  • Ejercicio: una reserva de tren construida con dataclasses
  • Una reserva de tren construida con dataclasses
    • Resultados de las pruebas de ejemplo

3 ejemplos ejecutables (Python) · 1 ejercicio con pruebas de ejemplo · unos 8 minutos

Abriendo esta parte con tu pase…

Esta parte se abre con cualquier pase de MySmartCoPilot.

  • Pase Pro · 1 semana79 ₹2,99 US$
  • 30 días179 ₹5,99 US$
  • 1 año1699 ₹45 US$

Un solo pago; un pase nunca se renueva. El precio que se muestra es el total que pagas: al pagar no se añaden comisiones ni cargos.

También abre todas las demás partes en profundidad de Learn y todas las herramientas Pro. Las ventas son definitivas, salvo cuando la ley te da un derecho de desistimiento, por ejemplo en la UE y el Reino Unido (política de reembolso (en inglés)).

Preguntas de entrevista

  1. De júnior a nivel medio · Calentamiento

    ¿Qué escribe el decorador @dataclass para una clase, y qué no hace?

    Lee en orden los campos anotados de la clase y escribe un __init__ que los recibe como parámetros, con sus valores por defecto; un __repr__ que muestra cada campo por su nombre; y un __eq__ que compara campo a campo dos objetos de la misma clase. También escribe __match_args__ para la coincidencia de patrones. Las opciones añaden más: order=True escribe los cuatro métodos de orden, frozen=True rechaza la asignación y escribe __hash__, slots=True añade __slots__ y kw_only=True hace que los parámetros sean solo por nombre. No comprueba los tipos en tiempo de ejecución: las anotaciones son solo información para los lectores y los comprobadores de tipos, así que un tipo equivocado se acepta salvo que __post_init__ lo compruebe. Tampoco copia los valores por defecto mutables; un valor por defecto de lista tiene que venir de field(default_factory=list).

  2. De júnior a nivel medio

    ¿Por qué @dataclass rechaza items: list = [] con un ValueError, mientras que una clase normal con def __init__(self, items=[]) se acepta? ¿Qué sale mal en la clase normal, y cómo lo arregla default_factory?

    Respuesta modelo, Pro. Las respuestas modelo se abren con cualquier pase; los precios están en el aviso de esta página. Primer paso y preguntas de seguimiento (en inglés)

  3. De nivel medio a sénior

    Un servicio guarda en caché resultados de búsqueda en un dict cuya clave es una dataclass Query. Alguien quitó frozen=True para permitir pequeños arreglos en las consultas, y ahora las búsquedas fallan con "unhashable type". Un compañero sugiere unsafe_hash=True. Evalúa la sugerencia y propón un diseño mejor.

    Respuesta modelo, Pro. Primer paso y preguntas de seguimiento (en inglés)

  4. De sénior a staff

    Una API HTTP recibe pedidos en JSON. ¿Los modelarías con dataclasses, con modelos de pydantic o con TypedDict? Compara la validación, la conversión, la velocidad y dónde va cada uno en el código, y di qué elegirías.

    Respuesta modelo, Pro. Primer paso y preguntas de seguimiento (en inglés)

Para recordar

  • @dataclass escribe __init__, __repr__ y __eq__ a partir de los campos anotados de una clase.
  • Los valores por defecto mutables necesitan field(default_factory=list); un [] sin más se rechaza.
  • frozen=True hace que los objetos sean inmutables y hashables; order=True, slots=True y kw_only=True añaden comparaciones, slots y parámetros solo por nombre.
  • Las anotaciones no se comprueban en tiempo de ejecución: valida en __post_init__, o usa pydantic para los datos de fuera.

Términos de esta lección

dataclass
Una clase decorada con @dataclass. El decorador escribe su __init__, su __repr__ y su __eq__ a partir de los campos anotados en su cuerpo, como name: str y quantity: int = 1, y opciones como frozen=True u order=True añaden más.
campo de una dataclass
Una línea con una anotación de tipo en el cuerpo de una dataclass, como quantity: int = 1. Su nombre pasa a ser un parámetro del __init__ escrito, con su valor por defecto si lo tiene; field() añade opciones como default_factory o compare=False.
dataclass congelada
Una dataclass declarada con frozen=True. Asignar a un campo después de crear el objeto lanza FrozenInstanceError, y con el eq=True por defecto el decorador también escribe un __hash__ a partir de los campos, así que los objetos pueden ser claves de un dict.

Referencias

Herramientas relacionadas

Informar de un problema con esta lección (en inglés)

Respuestas rápidas y búsqueda de herramientas

Escribe para buscar herramientas o para obtener una respuesta rápida (en inglés), por ejemplo 18% of 2500. Usa las flechas arriba y abajo para recorrer los resultados, Intro para elegir y Escape para cerrar.