typing.ReadOnly: campos imutáveis em TypedDict

Publicado em: 22/09/2026
Tempo de leitura: 6 minutos
Desenvolvedora trabalhando com tipagem estática e typing.ReadOnly no Python

typing.ReadOnly é um recurso de tipagem que permite declarar chaves somente leitura em TypedDict. Ele ajuda a representar estruturas de dados nas quais certos campos podem ser definidos durante a criação, mas não devem ser modificados depois. Isso é útil para configurações, identificadores, dados recebidos de APIs, registros de auditoria e objetos que precisam preservar invariantes sem abandonar a praticidade dos dicionários.

Neste guia, você vai entender como usar ReadOnly, quais problemas ele resolve, como os verificadores de tipo interpretam a anotação, quais limitações existem em tempo de execução e como integrar essa abordagem a projetos reais.

O problema que ReadOnly resolve

Um TypedDict descreve o formato esperado de um dicionário. Ele informa quais chaves existem e quais tipos de valores cada chave aceita. Entretanto, antes de ReadOnly, todas as chaves eram tratadas como modificáveis do ponto de vista da tipagem. Isso dificultava modelar dados que possuem campos estáveis, como um identificador gerado pelo sistema, a data de criação de um registro ou o código de uma transação.

Considere um cadastro de usuário. O nome pode ser atualizado, mas o identificador interno não deveria mudar depois que o registro foi criado. Com ReadOnly, essa intenção fica explícita na definição do tipo.

from typing import ReadOnly, TypedDict

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

Um verificador de tipos aceita a leitura de usuario['id'], mas sinaliza uma tentativa de atribuição como usuario['id'] = 99. As outras chaves continuam modificáveis.

ReadOnly atua na análise estática

É importante entender que ReadOnly não transforma o dicionário em um objeto imutável no tempo de execução. O Python continua usando um dict comum, e uma atribuição direta ainda pode acontecer quando o programa é executado. A proteção é oferecida pelo verificador de tipos, como Pyright, mypy ou ferramentas integradas ao editor.

Portanto, o benefício depende de uma rotina de análise estática no projeto. Em uma equipe, o ideal é executar o verificador localmente e também no pipeline de integração contínua. Assim, alterações indevidas são detectadas antes de chegar à produção.

Exemplo com configuração de aplicação

Configurações carregadas no início do programa frequentemente combinam campos fixos e campos ajustáveis. O ambiente e a versão de implantação podem ser somente leitura, enquanto o nível de log pode ser alterado por uma interface administrativa.

from typing import ReadOnly, TypedDict

class Configuracao(TypedDict):
    ambiente: ReadOnly[str]
    versao: ReadOnly[str]
    nivel_log: str
    timeout: float

config: Configuracao = {
    'ambiente': 'producao',
    'versao': '2.4.0',
    'nivel_log': 'INFO',
    'timeout': 10.0,
}

config['nivel_log'] = 'DEBUG'

A alteração do nível de log é válida. Já a tentativa de trocar o ambiente ou a versão deve ser sinalizada. Esse modelo torna a intenção da API muito mais clara para quem lê o código.

ReadOnly e campos opcionais

ReadOnly pode ser combinado com chaves opcionais. Para isso, use NotRequired quando a chave pode não existir. A ordem das anotações deve seguir o suporte do verificador utilizado.

from typing import NotRequired, ReadOnly, TypedDict

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

Nesse exemplo, cache_key pode estar ausente, mas, quando presente, não deve ser alterada. Isso é útil para respostas de serviços externos e metadados calculados por infraestrutura.

Herança e compatibilidade estrutural

TypedDict usa tipagem estrutural. Isso significa que a compatibilidade depende do conjunto de chaves e dos tipos, não apenas do nome da classe. A presença de chaves somente leitura afeta essa compatibilidade, especialmente quando uma função promete não modificar determinados campos.

Uma função que recebe um tipo com chaves somente leitura pode aceitar estruturas mais específicas desde que respeitem o contrato. Porém, permitir que um dicionário modificável seja tratado como somente leitura deve ser analisado pelo verificador, porque aliases diferentes podem apontar para o mesmo objeto. A regra prática é evitar conversões forçadas e deixar a ferramenta de tipagem avaliar a segurança.

Funções que recebem dados somente leitura

Uma vantagem importante aparece no desenho de funções. Ao declarar que uma chave é somente leitura, você comunica que a função pode consultar aquele valor, mas não deve substituí-lo.

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

def marcar_pago(pedido: Pedido) -> None:
    pedido['status'] = 'pago'
    # pedido['codigo'] = 'outro'  # erro de tipagem

Esse contrato reduz alterações acidentais em campos críticos. Ele também facilita revisões de código, pois a regra está próxima da definição dos dados.

Quando usar dataclass congelada

ReadOnly não substitui uma dataclass congelada. Uma dataclass(frozen=True) oferece uma barreira em tempo de execução para atribuições de atributos. Já TypedDict continua sendo um dicionário e é especialmente útil em dados JSON, integrações HTTP e estruturas que precisam manter compatibilidade com APIs baseadas em mapeamentos.

Use ReadOnly quando você precisa preservar o formato de dicionário e quer uma garantia estática por chave. Use uma classe imutável quando deseja comportamento, métodos, validação centralizada e proteção em tempo de execução.

Validação em tempo de execução

Como a anotação não impede alterações durante a execução, dados vindos de fontes externas ainda precisam ser validados. Você pode criar uma função de construção que confira tipos, presença de chaves e valores permitidos antes de retornar o TypedDict.

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

Esse padrão combina validação real com documentação estática. Em sistemas maiores, bibliotecas de validação também podem ser usadas, mas a distinção continua importante: tipagem e validação resolvem problemas diferentes.

Compatibilidade entre versões

Antes de adotar typing.ReadOnly, verifique a versão mínima do Python e o suporte do verificador de tipos do projeto. Em bases que precisam funcionar em versões anteriores, typing_extensions pode oferecer uma alternativa compatível. Mantenha as ferramentas de tipagem atualizadas e execute testes em todas as versões suportadas.

Também vale consultar a documentação oficial de typing e a especificação publicada em PEP 705. Essas fontes explicam as regras formais e os casos de compatibilidade.

Boas práticas

Marque como somente leitura apenas campos que realmente representam identidade, origem ou estado imutável. Se muitas chaves receberem a anotação, talvez uma classe imutável seja mais adequada. Evite usar cast para contornar erros sem investigar a causa, pois isso remove a proteção que motivou o uso de ReadOnly.

Adote nomes claros para os tipos, mantenha funções de construção pequenas e documente quando um campo é definido. Testes devem verificar o comportamento de validação e o pipeline deve executar o verificador de tipos.

Integração com outros recursos do Python

Para aprofundar o desenho de tipos, consulte os artigos da Academify sobre typing no Python, dataclasses no Python, dicionários em Python e JSON no Python. Esses temas ajudam a decidir entre dicionários tipados, classes de dados e validação de entradas.

Conclusão

typing.ReadOnly melhora a capacidade de representar contratos precisos em TypedDict. Ele permite distinguir campos editáveis de chaves que devem permanecer estáveis, reduz erros em APIs internas e torna a intenção do código visível para editores e ferramentas de análise.

A anotação não cria imutabilidade em tempo de execução, então deve ser combinada com validação, testes e análise estática contínua. Quando usada nos cenários corretos, oferece uma solução leve para dados estruturados que precisam continuar sendo dicionários, mas exigem regras mais claras de modificação.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Código Python representando argumentos posicionais com functools.Placeholder
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    functools.Placeholder: argumentos no meio do partial

    Aprenda functools.Placeholder no Python para reservar argumentos intermediários em partial e criar callbacks e adaptadores mais claros.

    Ler mais

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

    warnings.deprecated: marque APIs obsoletas

    Aprenda warnings.deprecated no Python para marcar APIs obsoletas, orientar migrações e integrar avisos com tipagem, testes e CI.

    Ler mais

    Tempo de leitura: 7 minutos
    21/09/2026
    Desenvolvedor monitorando a execução de código Python com sys.monitoring
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    sys.monitoring: profiling e observabilidade no Python

    Aprenda sys.monitoring no Python para criar profilers, cobertura, depuração e observabilidade com eventos seletivos e baixo overhead.

    Ler mais

    Tempo de leitura: 7 minutos
    21/09/2026
    Código Python em uma tela representando template strings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Template strings: interpolação estruturada no Python

    Aprenda como template strings preservam interpolações para gerar conteúdo com mais controle e segurança.

    Ler mais

    Tempo de leitura: 8 minutos
    20/09/2026
    Código Python sendo analisado para medir desempenho com perf_counter_ns
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    perf_counter_ns: meça desempenho em nanossegundos

    Aprenda a medir desempenho e latência com perf_counter_ns no Python usando nanossegundos, repetições e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    20/09/2026
    Código Python representando uma fila de prioridade com heapq max-heap
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    heapq max-heap: filas de prioridade máximas

    Aprenda a usar as funções de max-heap do módulo heapq para filas de prioridade, rankings e algoritmos eficientes no Python.

    Ler mais

    Tempo de leitura: 5 minutos
    19/09/2026