ReadOnly en Python: protege campos TypedDict

Publicado el: 29/08/2026
Tempo de leitura: 5 minutos
Ball python slithering on a sunlit gravel pathway outdoors.

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"] = 99

Python 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ático

El 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: bool

El 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 ejecuta

El 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: str

id 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: str

Las 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: str

La 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Person holding Python logo sticker with blurred background, highlighting programming focus.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Required y NotRequired: campos opcionales en TypedDict

    Aprende Required y NotRequired en Python para controlar claves obligatorias y opcionales de TypedDict sin confundir ausencia con None.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    TypeAliasType en Python: alias en runtime

    Aprende TypeAliasType en Python para crear alias explícitos, inspeccionarlos en runtime y modelar APIs genéricas reutilizables.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    overload en Python: firmas precisas

    Aprende typing.overload en Python para firmas precisas con Literal, None, genéricos, métodos y retornos dependientes de argumentos.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ClassVar en Python: separa clase e instancia

    Aprende ClassVar en Python para separar atributos de clase e instancia en dataclasses, registries, caches, herencia y contadores.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Macro shot capturing detailed patterns of a python in its natural surroundings.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Final en Python: protege constantes y herencia

    Aprende Final y @final en Python para proteger constantes, atributos, métodos y clases, comprendiendo los límites en runtime.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Annotated en Python: tipos con metadatos

    Aprende Annotated en Python para añadir metadatos a tipos, crear validación, schemas, unidades e integraciones con frameworks.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026