Dicionários são flexíveis, mas essa flexibilidade pode esconder erros. Um payload pode exigir id, nome e ativo, enquanto o código trata tudo como dict[str, object]. Nesse tipo genérico, o analisador não sabe quais chaves existem, quais são opcionais nem qual tipo corresponde a cada campo. typing.TypedDict resolve esse problema ao descrever a estrutura esperada de um dicionário sem alterar seu comportamento em runtime.
Neste guia, você aprenderá a criar TypedDict, definir campos obrigatórios e opcionais, usar NotRequired e Required, compor esquemas, representar respostas de APIs, refinar tipos e entender os limites entre validação estática e validação real de dados.
O que é TypedDict?
Um TypedDict é uma declaração de tipo para dicionários com chaves conhecidas. Em execução, o valor continua sendo um dict normal.
from typing import TypedDict
class Usuario(TypedDict):
id: int
nome: str
ativo: bool
usuario: Usuario = {
"id": 10,
"nome": "Ana",
"ativo": True,
}Mypy ou Pyright pode detectar uma chave ausente, um nome digitado incorretamente ou um valor com tipo incompatível.
usuario_errado: Usuario = {
"id": "10", # esperado int
"nome": "Ana",
"ativo": True,
}O Python não impede essa atribuição durante a execução. A proteção aparece quando o projeto executa um verificador estático.
Por que não usar apenas dict[str, object]?
dict[str, object] informa apenas que as chaves são strings e os valores podem ser qualquer objeto. Ele não expressa quais chaves são válidas nem relaciona cada chave ao seu tipo.
def mostrar(usuario: dict[str, object]) -> str:
return usuario["nome"].upper() # object não garante upper()Com TypedDict, o analisador sabe que usuario["nome"] é str.
def mostrar(usuario: Usuario) -> str:
return usuario["nome"].upper()Esse nível de precisão complementa os conceitos do guia sobre type hints em Python.
Campos opcionais com total=False
Por padrão, todas as chaves são obrigatórias. Use total=False quando todas puderem faltar.
class AtualizacaoUsuario(TypedDict, total=False):
nome: str
ativo: bool
email: strEsse formato é útil para operações PATCH, nas quais o cliente envia somente os campos que deseja alterar.
def atualizar(id: int, dados: AtualizacaoUsuario) -> None:
if "nome" in dados:
print(dados["nome"])Uma chave opcional não significa que o valor aceita None. São conceitos diferentes: a chave pode não existir; se existir, deve conter o tipo declarado.
NotRequired e Required
Quando apenas alguns campos são opcionais, use NotRequired. Quando a classe usa total=False e uma chave precisa continuar obrigatória, use Required.
from typing import NotRequired, Required, TypedDict
class Perfil(TypedDict):
id: int
nome: str
apelido: NotRequired[str]
foto: NotRequired[str]
class EventoParcial(TypedDict, total=False):
tipo: Required[str]
payload: object
origem: strEsses marcadores deixam o contrato legível e evitam criar várias classes apenas para controlar obrigatoriedade.
Herança e composição
TypedDict pode herdar de outro TypedDict.
class Entidade(TypedDict):
id: int
class Produto(Entidade):
nome: str
preco: float
estoque: intA herança ajuda a reutilizar campos comuns, mas não deve virar uma hierarquia complexa. Em APIs, pequenos esquemas específicos costumam ser mais claros do que um modelo gigante usado para criação, leitura, atualização e persistência.
TypedDict funcional
A sintaxe funcional é útil quando as chaves não são identificadores Python válidos.
Cabecalhos = TypedDict(
"Cabecalhos",
{
"content-type": str,
"x-request-id": str,
},
)Em código novo, a sintaxe de classe é normalmente mais fácil de ler e documentar.
Representando respostas de APIs
TypedDict funciona bem para payloads internos ou respostas externas já validadas.
class Endereco(TypedDict):
cidade: str
estado: str
cep: str
class ClienteAPI(TypedDict):
id: int
nome: str
endereco: Endereco
tags: list[str]
def nome_cidade(cliente: ClienteAPI) -> str:
return cliente["endereco"]["cidade"]Não anote diretamente o resultado de response.json() como se ele já fosse confiável. Dados externos precisam de validação antes de receber um tipo mais específico.
TypedDict não valida em runtime
Esta declaração não converte valores, não rejeita chaves extras e não gera mensagens de validação. Se o programa lê JSON, formulários ou filas, use validação explícita, dataclasses com parsing, Pydantic, attrs ou outra ferramenta apropriada.
import json
texto = '{"id": "dez", "nome": 99, "ativo": true}'
dados = json.loads(texto)
# dados existe em runtime, mesmo incompatível com UsuarioUma abordagem segura valida primeiro e só depois trata o resultado como Usuario.
Refinando chaves opcionais
Quando uma chave usa NotRequired, verifique sua presença antes de acessar.
class Resultado(TypedDict):
valor: int
aviso: NotRequired[str]
def imprimir(resultado: Resultado) -> None:
print(resultado["valor"])
if "aviso" in resultado:
print(resultado["aviso"])dict.get() também pode ser usado, mas o tipo normalmente inclui None, exigindo tratamento adequado.
Chaves extras e compatibilidade
TypedDict segue regras estruturais. Um dicionário tipado com campos adicionais pode ser aceito em alguns contextos quando contém tudo que o consumidor exige. Porém, atribuições, mutabilidade e obrigatoriedade tornam as regras mais rigorosas do que uma simples comparação de conjuntos.
Evite depender de detalhes surpreendentes de compatibilidade. Defina contratos pequenos e use o verificador escolhido no CI para manter comportamento consistente.
TypedDict como parâmetro e retorno
class NovoPedido(TypedDict):
cliente_id: int
itens: list[int]
cupom: NotRequired[str]
class PedidoCriado(TypedDict):
id: int
status: str
total: float
def criar_pedido(dados: NovoPedido) -> PedidoCriado:
return {
"id": 501,
"status": "criado",
"total": 149.90,
}A assinatura documenta a fronteira da função e melhora autocomplete, revisão e refatoração.
Usando Literal para discriminar variantes
TypedDict combina bem com Literal para representar mensagens diferentes.
from typing import Literal
class Sucesso(TypedDict):
tipo: Literal["sucesso"]
valor: int
class Falha(TypedDict):
tipo: Literal["falha"]
erro: str
Resposta = Sucesso | Falha
def processar(resposta: Resposta) -> str:
if resposta["tipo"] == "sucesso":
return str(resposta["valor"])
return resposta["erro"]O campo discriminador permite que o analisador refine automaticamente a variante.
Readonly e evolução de esquemas
Versões recentes do ecossistema de typing também oferecem recursos para indicar campos somente leitura em verificadores compatíveis. A disponibilidade depende da versão do Python e da ferramenta. Antes de adotar um recurso novo, confirme suporte no mypy ou Pyright usado pelo projeto.
Para evoluir APIs, acrescente campos opcionais de forma compatível e evite remover ou mudar tipos sem versionamento. TypedDict ajuda a localizar consumidores afetados durante a refatoração.
Erros comuns
- Confiar em TypedDict para validar JSON: ele é apenas tipagem estática.
- Confundir chave opcional com valor Optional: ausência e
Nonesão diferentes. - Usar um esquema para todas as operações: separe criação, atualização e resposta.
- Aplicar cast sem validar: o cast silencia o analisador, não corrige dados.
- Declarar object em todos os campos: isso elimina grande parte do benefício.
- Ignorar chaves extras e mutabilidade: contratos estruturais ainda possuem regras de compatibilidade.
TypedDict, dataclass ou Pydantic?
Use TypedDict quando o valor deve continuar sendo um dicionário e a principal necessidade é análise estática. Use dataclass quando quiser objetos com atributos, métodos e construção explícita. Use Pydantic quando precisar validar e converter dados externos, gerar schemas ou integrar com frameworks de API.
Essas ferramentas podem coexistir. Uma camada de entrada valida dados com Pydantic e uma camada interna usa TypedDict para estruturas leves ou interoperabilidade com bibliotecas baseadas em dicionários.
Conclusão
typing.TypedDict transforma dicionários informais em contratos verificáveis. Ele descreve chaves obrigatórias, opcionais, tipos aninhados e variantes discriminadas sem mudar o objeto em runtime.
A documentação oficial de TypedDict no módulo typing detalha totalidade, herança e marcadores de obrigatoriedade. Use TypedDict para estruturas conhecidas, execute um verificador estático regularmente e mantenha validação real nas fronteiras onde os dados não são confiáveis.







