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.







