Comprende lo que las anotaciones hacen realmente en tiempo de ejecución y saca el máximo partido a las dataclasses con frozen, slots y default_factory.
Abrir esta lección en KodokonLas anotaciones de tipo son metadatos, no restricciones: el intérprete las almacena en __annotations__ y nunca las comprueba en tiempo de ejecución. La comprobación es tarea de un analizador estático como mypy o pyright, antes de la ejecución. La consecuencia directa: un programa mal tipado se ejecuta sin protestar - y eso es intencionado. El tipado gradual te permite anotar poco a poco una base de código existente sin romper nada.
from typing import get_type_hints
def add(a: int, b: int) -> int:
return a + b
print(add("py", "thon"))
print(add.__annotations__)
print(get_type_hints(add))Desde Python 3.9, los tipos integrados son directamente genéricos: escribe list[int] o dict[str, float] sin importar typing.List. Las uniones se escriben int | None desde la versión 3.10. Para ligar el tipo de entrada al tipo de salida, usa un TypeVar - Python 3.12 incluso ofrece la sintaxis compacta def first[T](items: list[T]) -> T. Un punto que a menudo se pasa por alto: from __future__ import annotations vuelve perezosa cada anotación (se almacena como cadena de texto), lo que permite referenciar clases definidas más abajo y reduce el coste en el momento de la importación.
from collections.abc import Iterable, Iterator
from typing import TypeVar
T = TypeVar("T")
def dedupe(items: Iterable[T]) -> Iterator[T]:
seen: set[T] = set()
for item in items:
if item not in seen:
seen.add(item)
yield item
print(list(dedupe([3, 1, 3, 2, 1])))from dataclasses import dataclass, field
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
@dataclass
class Basket:
items: list[str] = field(default_factory=list)
total: int = 0
def __post_init__(self) -> None:
self.total = len(self.items)
point = Point(1.0, 2.0)
print(point, hash(point))
print(Basket(["book", "pen"]))Tres parámetros convierten @dataclass en una herramienta lista para producción. frozen=True hace que las instancias sean inmutables y, combinado con la igualdad generada, hashables - utilizables como claves de diccionario. slots=True (Python 3.10+) elimina el __dict__ por instancia: menos memoria, acceso más rápido a los atributos, y cualquier asignación de atributo inesperada falla. Por último, kw_only=True obliga a llamar con argumentos por nombre de forma explícita. __post_init__ es el lugar donde van los invariantes y los campos derivados. Para un agregado inmutable sencillo, NamedTuple sigue teniendo sentido; en cuanto hay comportamiento o validación, prefiere la dataclass.
add('py', 'thon') mientras la función está anotada como (a: int, b: int) -> int?items: list[str] = [] como valor por defecto de un campo de dataclass?list[str] no es una anotación válida para un campofrozen=True a una dataclass?