namedtuple y TypedDict: datos estructurados con tipo
namedtuple crea tuplas con campos nombrados, inmutables y ligeras. TypedDict añade type hints a dicts literales para que mypy pueda verificarlos. Ambas son alternativas valiosas a dataclass.
namedtuple básico y _fields
namedtuple crea una subclase de tupla cuyos campos tienen nombre. Es inmutable como las tuplas, ocupa la misma memoria, pero permite acceder a los campos por nombre en lugar de índice.
from collections import namedtuple
# Definición: nombre de la clase y campos como string o lista
Point = namedtuple("Point", ["x", "y"])
Color = namedtuple("Color", "red green blue") # también acepta string con espacios
# Crear instancias
p = Point(3, 4)
c = Color(255, 128, 0)
# Acceso por nombre (legible) o por índice (compatible con tupla)
print(p.x, p.y) # 3 4
print(p[0], p[1]) # 3 4 — mismo acceso
print(c.red) # 255
# Introspección
print(Point._fields) # ('x', 'y')
print(p._asdict()) # {'x': 3, 'y': 4}
# _replace: crea una copia con campos modificados (no muta el original)
p2 = p._replace(x=10)
print(p2) # Point(x=10, y=4)
print(p) # Point(x=3, y=4) — sin cambios
# Desempacar como tupla normal
x, y = p
print(x, y) # 3 4
# Valores por defecto (Python 3.6.1+)
Point3D = namedtuple("Point3D", ["x", "y", "z"], defaults=[0])
p3 = Point3D(1, 2)
print(p3) # Point3D(x=1, y=2, z=0)3 4
3 4
255
('x', 'y')
{'x': 3, 'y': 4}
Point(x=10, y=4)
Point(x=3, y=4)
3 4
Point3D(x=1, y=2, z=0)typing.NamedTuple — sintaxis moderna
typing.NamedTuple permite definir namedtuples con type hints y documentación, con una sintaxis de clase más clara y compatible con mypy.
from typing import NamedTuple, Optional
# Sintaxis moderna: clase con type hints
class Employee(NamedTuple):
name: str
department: str
salary: float
manager: Optional[str] = None # campo con valor por defecto
emp = Employee("Ana García", "Ingeniería", 75_000.0)
emp2 = Employee("Carlos", "Design", 65_000.0, manager="Ana García")
print(emp)
# Employee(name='Ana García', department='Ingeniería', salary=75000.0, manager=None)
print(emp2.manager) # Ana García
# Se puede serializar fácilmente
import json
data = emp._asdict()
print(json.dumps(data, ensure_ascii=False))
# {"name": "Ana García", "department": "Ingeniería", "salary": 75000.0, "manager": null}
# Compatible con isinstance y tipado
print(isinstance(emp, tuple)) # True
print(isinstance(emp, Employee)) # True
# Métodos propios — pueden añadirse en la clase
class Vector(NamedTuple):
x: float
y: float
def magnitude(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
v1 = Vector(3.0, 4.0)
print(v1.magnitude()) # 5.0
print(v1 + Vector(1.0, 1.0)) # Vector(x=4.0, y=5.0)Employee(name='Ana García', department='Ingeniería', salary=75000.0, manager=None)
Ana García
{"name": "Ana García", "department": "Ingeniería", "salary": 75000.0, "manager": null}
True
True
5.0
Vector(x=4.0, y=5.0)TypedDict — dicts tipados para mypy
TypedDict declara el "esquema" de un diccionario para que mypy pueda verificar que los campos tienen los tipos correctos. A diferencia de dataclass, sigue siendo un dict normal en runtime.
from typing import TypedDict
# Definición básica
class Movie(TypedDict):
title: str
year: int
rating: float
# Crear un Movie: es un dict normal en runtime
film: Movie = {
"title": "Dune: Part Two",
"year": 2024,
"rating": 8.5,
}
print(film["title"]) # Dune: Part Two
print(type(film)) # <class 'dict'>
# mypy verificaría esto en tiempo de análisis estático:
# film["year"] = "not a number" # ← mypy error: expected int
# TypedDict con herencia
class MovieWithCast(Movie):
director: str
cast: list[str]
full: MovieWithCast = {
"title": "Dune",
"year": 2021,
"rating": 8.0,
"director": "Denis Villeneuve",
"cast": ["Timothée Chalamet", "Zendaya"],
}
# Útil para tipar respuestas de API JSON
class APIResponse(TypedDict):
status: int
data: dict
message: str
def parse_response(raw: dict) -> APIResponse:
return APIResponse(
status=raw.get("status", 200),
data=raw.get("data", {}),
message=raw.get("message", "ok"),
)Dune: Part Two
<class 'dict'>Required y NotRequired (Python 3.11+)
Required y NotRequired permiten mezclar campos obligatorios y opcionales dentro de un mismo TypedDict, sin necesidad de herencia.
from typing import TypedDict, Required, NotRequired
# total=False hace que todos los campos sean opcionales por defecto
class Config(TypedDict, total=False):
host: str
port: int
debug: bool
# Mezclar: algunos requeridos, otros opcionales
class UserProfile(TypedDict):
id: Required[int] # siempre obligatorio
username: Required[str] # siempre obligatorio
bio: NotRequired[str] # opcional
avatar_url: NotRequired[str] # opcional
# En Python 3.11+ con total=False se puede hacer más limpio:
class PartialUser(TypedDict, total=False):
bio: str
avatar_url: str
class FullUser(PartialUser):
id: int # Required por defecto (total=True heredado)
username: str
# Ejemplo real: actualización parcial en una API REST
def update_user(user_id: int, updates: PartialUser) -> None:
# Solo los campos presentes se actualizan
fields = ", ".join(f"{k} = ?" for k in updates)
values = list(updates.values())
print(f"UPDATE users SET {fields} WHERE id = {user_id}")
print(f"Valores: {values}")
update_user(42, {"bio": "Developer en Python"})UPDATE users SET bio = ? WHERE id = 42
Valores: ['Developer en Python']Comparación: namedtuple vs dataclass vs TypedDict
Cada estructura tiene su lugar. La elección depende de si necesitas mutabilidad, verificación de tipos en runtime, compatibilidad con dict, o rendimiento mínimo.
| namedtuple | dataclass | TypedDict |
|---|---|---|
| Inmutable (como tupla) | Mutable por defecto (frozen=True opcional) | Mutable (es un dict) |
| Menor memoria (como tupla) | Mayor memoria (slots opcional) | Memoria de dict estándar |
| Sin verificación runtime de tipos | __post_init__ para validar | Solo verificación estática (mypy) |
| No se puede pasar donde se espera dict | Requiere asdict() para serializar | Es un dict — compatible con json.dumps() directo |
Usa namedtuple para datos ligeros e inmutables (coordenadas, registros de base de datos en lectura). Usa dataclass cuando necesites lógica, validación o mutabilidad. Usa TypedDict para tipar respuestas JSON de APIs sin deserialización extra.