typing.ReadOnly permite marcar claves individuales de un TypedDict como de solo lectura para el análisis estático. Es útil cuando un registro con forma de diccionario contiene valores que los consumidores pueden leer, pero no deberían modificar después de su creación, como identificadores, timestamps, versiones, códigos de auditoría y campos calculados por el servidor.
ReadOnly no congela el diccionario en runtime. Documenta y verifica un contrato de escritura durante el análisis estático. Esta guía cubre declaraciones, campos obligatorios y opcionales, herencia, subtipado, factories, modelos de API, limitaciones de runtime y alternativas como dataclasses congeladas o MappingProxyType.
El problema de los diccionarios mutables
from typing import TypedDict
class Usuario(TypedDict):
id: int
nombre: str
usuario: Usuario = {"id": 1, "nombre": "Ana"}
usuario["id"] = 99Python permite la asignación. Sin embargo, en muchos dominios el identificador debería fijarse durante la creación. Un TypedDict normal no distingue campos editables de campos protegidos.
Declarar ReadOnly
from typing import ReadOnly, TypedDict
class Usuario(TypedDict):
id: ReadOnly[int]
nombre: str
usuario: Usuario = {"id": 1, "nombre": "Ana"}
usuario["nombre"] = "Ana Silva" # permitido
usuario["id"] = 2 # error estáticoEl valor de id sigue siendo legible como entero. Las asignaciones, eliminaciones y otras operaciones de mutación sobre esa clave deberían ser rechazadas por el analizador.
ReadOnly actúa por campo
class Registro(TypedDict):
creado_en: ReadOnly[str]
version: ReadOnly[int]
titulo: str
activo: boolEl diccionario completo no se vuelve de solo lectura. titulo y activo siguen siendo editables. Esto modela objetos con identidad estable y estado mutable.
No existe protección automática en runtime
registro: Registro = {
"creado_en": "2026-07-26",
"version": 1,
"titulo": "Ejemplo",
"activo": True,
}
registro["version"] = 10 # Python lo ejecutaEl código que no pasa por análisis estático todavía puede modificar la clave. Para inmutabilidad real, usa una dataclass congelada, una clase con setters controlados, un mapping inmutable o MappingProxyType. Consulta MappingProxyType en Python.
ReadOnly y presencia de claves
ReadOnly responde “¿se puede escribir después?”. La totalidad responde “¿la clave debe existir?”. Son dimensiones independientes.
from typing import NotRequired, ReadOnly, Required, TypedDict
class Respuesta(TypedDict, total=False):
id: Required[ReadOnly[int]]
cache: NotRequired[ReadOnly[str]]
mensaje: strid es obligatorio y de solo lectura. cache es opcional y de solo lectura cuando existe. mensaje es opcional y editable porque la clase usa total=False.
Orden de calificadores
Las herramientas modernas entienden combinaciones de Required, NotRequired y ReadOnly. Elige un estilo consistente y compruébalo con el analizador del proyecto. La intención debe ser evidente durante la revisión.
Campos calculados
class Pedido(TypedDict):
subtotal: float
descuento: float
total: ReadOnly[float]Una factory calcula total y el resto del código lo trata como estable. El contrato reduce ediciones accidentales de un valor derivado.
Factories y valores iniciales
def crear_pedido(subtotal: float, descuento: float) -> Pedido:
return {
"subtotal": subtotal,
"descuento": descuento,
"total": subtotal - descuento,
}ReadOnly no impide construir el diccionario. Permite proporcionar el valor inicial y restringe escrituras posteriores a través de referencias tipadas.
Usa modelos separados para patches
class ActualizarPedido(TypedDict, total=False):
subtotal: float
descuento: float
def actualizar(pedido_id: int, cambios: ActualizarPedido) -> None:
...Un tipo de actualización dedicado omite id y total. La interfaz estática hace más difícil expresar payloads inválidos.
Subtipado y seguridad de escritura
Los campos de solo lectura pueden permitir relaciones más flexibles porque el consumidor no puede sustituir sus valores. Una estructura con un valor más específico puede verse mediante un contrato general de lectura.
class Animal: ...
class Perro(Animal): ...
class FuenteAnimal(TypedDict):
item: ReadOnly[Animal]
class FuentePerro(TypedDict):
item: ReadOnly[Perro]La compatibilidad exacta depende de la especificación y del analizador. La idea central es que quitar escritura reduce riesgos de variancia.
Herencia de TypedDict
class EventoBase(TypedDict):
id: ReadOnly[str]
creado_en: ReadOnly[str]
class EventoUsuario(EventoBase):
usuario_id: int
accion: strLas subclases heredan los contratos. No conviertas silenciosamente un campo protegido en editable, porque romperías a consumidores que confían en la base.
Representaciones públicas e internas
class UsuarioPublico(TypedDict):
id: ReadOnly[int]
nombre: ReadOnly[str]
class UsuarioInterno(TypedDict):
id: int
nombre: str
hash_password: strLa implementación puede modificar el registro interno y exponer un contrato público más restringido. Estructuras parecidas no son automáticamente intercambiables; verifica las asignaciones con tu analizador.
ReadOnly en parámetros
def mostrar(usuario: UsuarioPublico) -> str:
return f"{usuario['id']}: {usuario['nombre']}"El parámetro comunica que las claves protegidas no deberían escribirse. Otras claves no protegidas todavía podrían cambiar. Para inmutabilidad completa de runtime, elige otra abstracción.
Copiar en lugar de mutar
def renombrar(usuario: UsuarioPublico, nombre: str) -> UsuarioPublico:
return {"id": usuario["id"], "nombre": nombre}ReadOnly restringe la mutación de la referencia existente, pero no prohíbe construir otro diccionario. Los patrones copy-on-write preservan registros publicados.
Serialización
ReadOnly no cambia JSON, pickle, almacenamiento ni transporte. La clave se serializa normalmente. El servidor debe validar datos externos y rechazar intentos de controlar valores protegidos.
Modelos HTTP
Una respuesta puede marcar id, created_at y checksum como ReadOnly. El modelo de entrada debería omitirlos. Separar creación, patch y respuesta produce APIs más claras.
Frameworks de validación
Algunos frameworks interpretan ReadOnly al generar schemas; otros lo ignoran. Comprueba la integración exacta. Nunca dependas solo de una anotación para autorización, integridad o seguridad de runtime.
Compatibilidad de versiones
Cuando typing.ReadOnly no esté disponible, impórtalo desde typing_extensions. Las bibliotecas deben declarar la dependencia y ejecutar pruebas estáticas en todas las versiones soportadas.
Errores comunes
- Suponer que el diccionario quedó congelado: ReadOnly es una regla estática.
- Usar un modelo para crear, actualizar y responder: cada operación puede necesitar otro contrato.
- Confundir ReadOnly con NotRequired: escritura y presencia son distintas.
- Eliminar una clave protegida: borrar también es mutar.
- Confiar en un framework sin verificar soporte: el runtime puede no cambiar.
- Hacer editable una clave protegida heredada: rompe sustitución segura.
Ejemplo completo: documento versionado
from typing import ReadOnly, TypedDict
class Documento(TypedDict):
id: ReadOnly[str]
creado_en: ReadOnly[str]
version: ReadOnly[int]
titulo: str
contenido: str
def crear_documento(id_: str, titulo: str, contenido: str) -> Documento:
return {
"id": id_,
"creado_en": "2026-07-26T12:00:00Z",
"version": 1,
"titulo": titulo,
"contenido": contenido,
}
def editar(documento: Documento, titulo: str, contenido: str) -> Documento:
return {
**documento,
"version": documento["version"] + 1,
"titulo": titulo,
"contenido": contenido,
}La función crea una nueva versión en lugar de mutar el registro existente. Dependiendo del analizador, reemplazar una clave ReadOnly durante la construcción puede requerir un tipo interno o una factory dedicada. El objetivo público es mantener estables las referencias publicadas.
Cuándo elegir otra herramienta
Usa dataclass congelada para objetos realmente inmutables con atributos. Usa MappingProxyType para una vista de mapping sin escritura en runtime. Usa una clase normal para validación e invariantes. Usa ReadOnly cuando la forma pública debe seguir siendo un diccionario y necesitas protección estática de escritura.
Conclusión
typing.ReadOnly añade una dimensión valiosa a TypedDict: los consumidores pueden leer ciertas claves, pero no deberían reasignarlas. Mejora modelos de respuesta, eventos, documentos versionados y datos con identidad estable.
La documentación oficial de ReadOnly en Python define el recurso. Combínalo con modelos separados para creación y actualización, ejecuta un analizador y añade protección de runtime cuando la integridad dependa realmente de impedir mutaciones.







