typing.ReadOnly: campos de solo lectura en TypedDict

Publicado el: 22/09/2026
Tempo de leitura: 6 minutos
Desarrolladora trabajando con tipado estático y typing.ReadOnly en Python

typing.ReadOnly permite declarar claves de solo lectura dentro de un TypedDict. Es útil cuando una estructura con forma de diccionario contiene campos que pueden definirse al crear el registro, pero que no deberían reemplazarse después. Algunos ejemplos habituales son identificadores, fechas de creación, metadatos de despliegue, códigos de transacción y valores producidos por servicios confiables.

En esta guía aprenderás qué significa ReadOnly, cómo lo interpretan los verificadores de tipos, por qué no convierte el diccionario en un objeto inmutable durante la ejecución y cómo aplicarlo de manera segura en proyectos reales.

El problema que resuelve ReadOnly

Un TypedDict describe la forma esperada de un diccionario. Indica qué claves existen y qué tipo de valor acepta cada una. Sin claves de solo lectura, todos los campos suelen considerarse modificables desde el punto de vista del análisis estático. Eso dificulta expresar un contrato frecuente: el registro puede cambiar, pero algunos valores deben permanecer estables.

Piensa en un usuario. El nombre y el correo pueden actualizarse, mientras que el identificador interno no debería cambiar después de crear el registro.

from typing import ReadOnly, TypedDict

class Usuario(TypedDict):
    id: ReadOnly[int]
    nombre: str
    email: str

El verificador permite leer usuario['id'], pero marca como error una asignación como usuario['id'] = 99. Las demás claves siguen siendo editables.

Protección estática, no inmutabilidad real

ReadOnly no cambia la implementación del diccionario. Durante la ejecución continúa siendo un dict normal. Python no bloquea por sí mismo una asignación a la clave anotada. La protección aparece en herramientas como Pyright, mypy, inspecciones del editor o comprobaciones ejecutadas en integración continua.

Esta diferencia es esencial. La tipificación comprueba el uso previsto antes de ejecutar el programa, mientras que la validación comprueba los datos reales en tiempo de ejecución. Los proyectos sólidos suelen necesitar ambas capas.

Ejemplo con configuración

Una configuración de aplicación puede mezclar valores fijos y ajustables. El entorno y la versión de despliegue pueden ser estables, mientras que el nivel de registro y el tiempo de espera pueden cambiar.

from typing import ReadOnly, TypedDict

class Configuracion(TypedDict):
    entorno: ReadOnly[str]
    version: ReadOnly[str]
    nivel_log: str
    timeout: float

config: Configuracion = {
    'entorno': 'produccion',
    'version': '2.4.0',
    'nivel_log': 'INFO',
    'timeout': 10.0,
}

config['nivel_log'] = 'DEBUG'

Cambiar el nivel de log es válido. Reemplazar el entorno o la versión debería generar un error de tipado. La definición comunica claramente el ciclo de vida de cada campo.

Claves opcionales de solo lectura

Una clave de solo lectura también puede ser opcional. Utiliza NotRequired cuando el campo puede no estar presente, pero no debe reemplazarse después de aparecer.

from typing import NotRequired, ReadOnly, TypedDict

class RespuestaAPI(TypedDict):
    request_id: ReadOnly[str]
    resultado: str
    cache_key: NotRequired[ReadOnly[str]]

En este caso, cache_key puede faltar. Cuando existe, los consumidores deben tratarla como metadato estable. Este diseño encaja bien con respuestas HTTP, eventos, mensajes y proyecciones de base de datos.

Contratos de funciones

Las claves de solo lectura mejoran las firmas de funciones porque dejan claro qué partes de la estructura pueden consultarse sin ser reemplazadas.

class Pedido(TypedDict):
    codigo: ReadOnly[str]
    estado: str
    total: float

def marcar_pagado(pedido: Pedido) -> None:
    pedido['estado'] = 'pagado'
    # pedido['codigo'] = 'otro'  # error de tipado

El contrato es más preciso que una anotación genérica con dict. El código representa identidad, mientras que el estado está diseñado para evolucionar.

Tipado estructural y alias

TypedDict utiliza tipado estructural. La compatibilidad depende de las claves disponibles y de sus tipos, no solo del nombre de la clase. Los calificadores de solo lectura participan en ese análisis, especialmente cuando varios alias hacen referencia al mismo diccionario.

Evita conversiones amplias con cast que eliminen la información de solo lectura. Si necesitas adaptar datos, considera crear una copia o definir un tipo de entrada más específico. Deja que el verificador compruebe si la relación es segura.

ReadOnly frente a dataclasses congeladas

ReadOnly no sustituye a @dataclass(frozen=True). Una dataclass congelada ofrece una barrera en tiempo de ejecución contra la asignación normal de atributos. También admite métodos, propiedades y comportamiento de dominio. Un TypedDict sigue siendo ideal cuando los datos deben mantener semántica de diccionario, como JSON, configuraciones y cargas HTTP.

Usa claves de solo lectura cuando solo algunos campos requieren protección estática. Elige una clase inmutable cuando todo el objeto deba comportarse como un valor y la protección en ejecución sea importante.

Validación en tiempo de ejecución

Las entradas externas deben validarse aunque el tipo use ReadOnly. Las anotaciones no verifican automáticamente el JSON recibido por red, los datos de un archivo ni los diccionarios creados por código sin tipos.

def crear_usuario(datos: dict[str, object]) -> Usuario:
    identificador = datos.get('id')
    nombre = datos.get('nombre')
    email = datos.get('email')
    if not isinstance(identificador, int):
        raise ValueError('id invalido')
    if not isinstance(nombre, str) or not isinstance(email, str):
        raise ValueError('datos invalidos')
    return {'id': identificador, 'nombre': nombre, 'email': email}

La función valida los valores reales y devuelve una estructura compatible con el contrato estático. En sistemas grandes puedes usar una biblioteca de validación, pero la separación sigue siendo la misma.

Compatibilidad entre versiones

Antes de adoptar la función, confirma la versión mínima de Python y el soporte del verificador elegido. Los proyectos que funcionan en versiones anteriores pueden usar una implementación compatible desde typing_extensions. Mantén actualizado el intérprete y las herramientas de análisis.

Consulta la documentación oficial de typing y la especificación de PEP 705 para conocer las reglas formales.

Buenas prácticas de diseño

Reserva ReadOnly para campos que representen identidad, procedencia, metadatos generados o invariantes importantes. No marques todas las claves automáticamente. Si casi toda la estructura debe ser inmutable, probablemente una dataclass congelada sea más clara.

No uses cast solo para ocultar errores. Investiga si la modificación es realmente válida. Centraliza la construcción y la validación, documenta cuándo se define cada valor estable y ejecuta el verificador en integración continua.

Migración gradual

Introduce la anotación poco a poco. Empieza por identificadores y fechas que ya se consideran estables por convención. Ejecuta el verificador, corrige las mutaciones reales y añade pruebas sobre los flujos de creación y actualización. Así evitas que una migración masiva oculte problemas de diseño.

Cuando un proceso necesita legítimamente otro valor, crea un nuevo registro o utiliza tipos diferentes para distintas etapas del ciclo de vida. Por ejemplo, un borrador y un registro persistido pueden tener contratos separados.

Recursos relacionados

Para ampliar el tema, revisa en Academify las guías sobre typing en Python, dataclasses en Python, diccionarios en Python y JSON en Python. Estos contenidos ayudan a elegir entre mapeos tipados, clases de datos y validación de entradas.

Conclusión

typing.ReadOnly hace que los contratos de TypedDict sean más expresivos. Permite distinguir claves estables de campos editables, ayuda a detectar reemplazos accidentales y mejora la documentación de APIs basadas en diccionarios.

Como la protección es estática, debe combinarse con validación, pruebas y un verificador ejecutado de forma consistente. Usado de manera selectiva, ofrece una solución ligera para conservar invariantes importantes sin perder la compatibilidad y la comodidad de los diccionarios de Python.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Código Python que representa argumentos posicionales con functools.Placeholder
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    functools.Placeholder: argumentos medios en partial

    Aprende functools.Placeholder en Python para reservar argumentos intermedios en partial y crear callbacks y adaptadores más claros.

    Ler mais

    Tempo de leitura: 6 minutos
    22/09/2026
    Código Python con aviso de API obsoleta usando warnings.deprecated
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    warnings.deprecated: marca APIs obsoletas

    Aprende warnings.deprecated en Python para marcar APIs obsoletas, orientar migraciones e integrar avisos con tipado, pruebas, documentación y CI.

    Ler mais

    Tempo de leitura: 6 minutos
    21/09/2026
    Ingeniero de software monitorizando la ejecución de código Python con sys.monitoring
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    sys.monitoring: profiling y observabilidad en Python

    Aprende sys.monitoring en Python para crear profilers, cobertura, depuración y observabilidad con eventos selectivos y overhead controlado.

    Ler mais

    Tempo de leitura: 8 minutos
    21/09/2026
    Código Python y template strings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Template strings: interpolación estructurada en Python

    Aprende cómo las template strings conservan interpolaciones para una renderización estructurada.

    Ler mais

    Tempo de leitura: 8 minutos
    20/09/2026
    Código Python medido para analizar rendimiento con perf_counter_ns
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    perf_counter_ns: mide rendimiento en nanosegundos

    Aprende a medir rendimiento y latencia con perf_counter_ns en Python usando nanosegundos, repeticiones y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    20/09/2026
    Código Python que representa una cola de prioridad con heapq max-heap
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    heapq max-heap: colas de prioridad máxima

    Aprende las funciones max-heap de heapq para colas de prioridad, rankings, planificadores y algoritmos eficientes en Python.

    Ler mais

    Tempo de leitura: 5 minutos
    19/09/2026