Trilha de aprendizado · Nível 12 · Tutorial 10

Descrever registros com TypedDict

Representar dicionários com campos conhecidos e tipos específicos por chave, distinguindo campos ausentes de campos cujo valor pode ser None.

  • Nível: Intermediário
  • Duração: 18 min
  • 7 passos
Descrever registros com TypedDict

O que você vai percorrer

  1. Um tipo para cada chave Reconheça quando um dicionário armazena valores de um mesmo tipo e quando ele representa um registro com campos conhecidos e contratos próprios. 2 min
  2. Declarar um registro com campos obrigatórios Declare um TypedDict pela sintaxe de classe e construa um registro de tarefa compatível. 3 min
  3. Consultar e atualizar campos com precisão Use chaves literais para preservar o tipo de cada campo de um TypedDict ao consultar, atualizar e passar registros para funções. 3 min
  4. Separar ausência de chave e valor None Modele separadamente se uma chave precisa existir e se seu valor pode ser None. 3 min
  5. Interpretar e corrigir diagnósticos do mypy Use os diagnósticos do mypy para localizar incompatibilidades em registros TypedDict e corrigir o código sem enfraquecer seu contrato. 3 min
  6. Reconhecer os limites em tempo de execução Distingua o contrato estático de TypedDict do comportamento real de dicionários durante a execução. 2 min
  7. Aplicação final: modelar e usar um registro Integre TypedDict, NotRequired, valores None, consultas, atualizações e verificação com mypy em um pequeno programa local. 3 min

O que você vai aprender

  • Declarar um TypedDict para um registro com campos heterogêneos.
  • Marcar campos que podem estar ausentes com NotRequired.
  • Identificar erros estáticos na construção, consulta e atualização de registros.
  • Explicar por que TypedDict não valida um dicionário recebido em tempo de execução.

Antes de começar

  • Anotar coleções e estruturas aninhadas
  • Tratar valores opcionais e uniões de tipos

Passo 1 de 7

Um tipo para cada chave

Reconheça quando um dicionário armazena valores de um mesmo tipo e quando ele representa um registro com campos conhecidos e contratos próprios.

Um dicionário pode ser um registro

Campos conhecidos de uma tarefa

Considere os dados de uma tarefa: ela tem um título, uma prioridade e a quantidade estimada de minutos.

{"titulo": "Revisar proposta", "prioridade": 2, "minutos_estimados": 30}

Aqui, as chaves têm papéis conhecidos. titulo deve guardar texto, enquanto prioridade e minutos_estimados devem guardar inteiros. Esse uso de dicionário é um registro: um conjunto de campos nomeados, cada qual com seu próprio contrato.

Mapa dos campos do registro

Observe que o tipo esperado depende da chave consultada.

Diagrama de um registro de tarefa com três chaves ligadas aos seus tipos: título ligado a texto, prioridade ligada a inteiro e minutos estimados ligado a inteiro.

Em um registro, o nome do campo determina o tipo esperado para seu valor.

O limite de um tipo único para os valores

Uma união não vincula tipo à chave

Você poderia anotar esses dados assim:

tarefa: dict[str, str | int] = {
    "titulo": "Revisar proposta",
    "prioridade": 2,
    "minutos_estimados": 30,
}

Essa anotação informa que qualquer valor do dicionário pode ser str ou int. Ela não expressa que titulo é especificamente str, nem que prioridade é especificamente int.

Portanto, dict[str, str | int] é útil quando todas as chaves seguem o mesmo contrato de valores possíveis, mas perde a relação entre cada nome de campo e seu tipo próprio.

Dois contratos diferentes

Compare as informações descritas por cada anotação.

python
# Mesmo contrato para todos os valores:
tarefa_generica: dict[str, str | int]

# Contrato desejado para um registro:
# "titulo" -> str
# "prioridade" -> int
# "minutos_estimados" -> int
#
# TypedDict permite descrever essa relação por chave.

TypedDict descreve contratos por campo

A informação que faltava

TypedDict é um recurso de tipagem para descrever dicionários que funcionam como registros. Ele permite declarar, estaticamente, quais chaves são conhecidas e qual tipo pertence a cada uma.

Assim, em vez de dizer apenas “os valores podem ser texto ou inteiro”, você pode representar o contrato preciso: titulo corresponde a texto; prioridade e minutos_estimados, a inteiros.

Nos próximos passos, você vai escrever essa declaração e usar o contrato ao consultar e atualizar os campos.

Mapeamento uniforme x registro tipado

A diferença central é onde está o contrato de tipo.

Comparação lado a lado: à esquerda, várias chaves de um dicionário apontam para o mesmo conjunto de tipos possíveis; à direita, cada chave conhecida aponta para um tipo específico.

dict[str, str | int] repete um contrato para todas as chaves; TypedDict associa um contrato a cada campo.

Escolha o contrato adequado

Mapeamento ou registro?

Associe cada situação ao contrato mais adequado.

Toque em um item e depois no par correspondente.

Passo 2 de 7

Declarar um registro com campos obrigatórios

Declare um TypedDict pela sintaxe de classe e construa um registro de tarefa compatível.

Declare o contrato do registro

Classe que descreve chaves

Com Python 3.12 ou superior, importe TypedDict de typing. Em seguida, declare uma classe cujos atributos anotados representam as chaves e os tipos dos valores de um registro.

No TypedDict pela sintaxe de classe, todos os campos são obrigatórios por padrão.

Contrato de uma tarefa

python
from typing import TypedDict

class Tarefa(TypedDict):
    titulo: str
    prioridade: int
    concluida: bool

Campos declarados e seus valores

Diagrama ligando os campos titulo, prioridade e concluida de Tarefa aos valores de texto, número inteiro e valor booleano em um dicionário.

Cada nome de campo possui seu próprio tipo de valor.

Construa um valor compatível

Anote a variável

Use o nome do TypedDict na anotação da variável e atribua um literal de dicionário. Para ser compatível, o literal precisa incluir titulo, prioridade e concluida, com os tipos definidos no contrato.

Registro completo

python
tarefa: Tarefa = {
    "titulo": "Enviar relatório",
    "prioridade": 2,
    "concluida": False,
}

Dica

Contrato estático

Tarefa descreve o formato esperado pelo verificador de tipos. Os nomes das chaves no literal devem corresponder aos campos declarados, e cada valor deve respeitar o tipo daquele campo.

Pratique a declaração

Complete a anotação

Considere a classe Tarefa(TypedDict) já declarada. Complete a lacuna:

nova_tarefa: ____ = {"titulo": "Estudar", "prioridade": 1, "concluida": False}

Passo 3 de 7

Consultar e atualizar campos com precisão

Use chaves literais para preservar o tipo de cada campo de um TypedDict ao consultar, atualizar e passar registros para funções.

Cada chave conserva seu próprio tipo

Consultar não produz uma união genérica

Considere o registro Tarefa já declarado com titulo: str, prioridade: int e concluida: bool. Ao acessar uma chave literal conhecida, o mypy usa o tipo declarado especificamente para aquela chave — não uma união de todos os tipos do registro.

Chave, tipo e operação

A mesma estrutura oferece resultados de tipos diferentes conforme a chave consultada.

Diagrama de um registro de tarefa com as chaves titulo, prioridade e concluida apontando respectivamente para valores texto, número inteiro e booleano, junto de operações compatíveis para cada resultado.

A chave literal determina o tipo que o verificador associa ao valor acessado.

Leituras tipadas por chave

Cada expressão entre colchetes tem seu tipo próprio.

python
tarefa: Tarefa = {
    "titulo": "Revisar proposta",
    "prioridade": 2,
    "concluida": False,
}

quantidade_caracteres = len(tarefa["titulo"])  # str
proxima_prioridade = tarefa["prioridade"] + 1   # int

if not tarefa["concluida"]:                     # bool
    print(tarefa["titulo"])

Atualize de acordo com o campo

A chave também orienta a escrita

Na atualização por colchetes, o novo valor deve ser compatível com o tipo da chave escolhida. Assim, tarefa["prioridade"] = 3 mantém o contrato; já atribuir um texto a essa chave viola o tipo declarado.

Atualização compatível

O tipo esperado depende da chave à esquerda da atribuição.

python
tarefa["titulo"] = "Enviar proposta"
tarefa["prioridade"] = 3
tarefa["concluida"] = True

# Incompatível com o contrato de Tarefa:
# tarefa["prioridade"] = "alta"

Dica

Pense no contrato por campo

Não trate o registro como se todos os valores tivessem o tipo str | int | bool. Em um TypedDict, cada nome de campo conhecido leva ao seu contrato específico.

Tipos preservados dentro de funções

Anote o parâmetro como o registro

Ao receber Tarefa como parâmetro, a função conserva as informações de cada campo. Isso permite usar operações de texto em titulo, cálculos em prioridade e condições em concluida.

Consumir uma Tarefa

A anotação evita que o parâmetro seja visto apenas como um dicionário de valores mistos.

python
def resumo(tarefa: Tarefa) -> str:
    estado = "concluída" if tarefa["concluida"] else "pendente"
    nivel = tarefa["prioridade"] + 1
    return f"{tarefa['titulo']} — {estado} (nível {nivel})"

print(resumo(tarefa))

Pratique a leitura do contrato

Qual expressão é compatível?

Para uma variável tarefa: Tarefa com titulo: str, prioridade: int e concluida: bool, qual expressão respeita o contrato?

Passo 4 de 7

Separar ausência de chave e valor None

Modele separadamente se uma chave precisa existir e se seu valor pode ser None.

Duas decisões independentes

Presença não é o mesmo que valor

Ao descrever um campo, responda a duas perguntas separadas:

  1. A chave precisa estar no dicionário?
  2. Se estiver, o valor pode ser None?

NotRequired[...] responde à primeira pergunta: a chave pode faltar. Já T | None responde à segunda: a chave existe, mas pode guardar None.

Por padrão, um campo declarado em um TypedDict continua obrigatório.

Matriz de presença e valor

As quatro combinações possíveis para um campo de registro.

Diagrama em matriz com quatro cartões: chave obrigatória com texto, chave obrigatória com texto ou None, chave opcional com texto e chave opcional com texto ou None.

A obrigatoriedade da chave e a aceitação de None são propriedades independentes.

Declarando a intenção

Campos opcionais e valores opcionais

Python 3.12+

python
from typing import NotRequired, TypedDict

class Tarefa(TypedDict):
    titulo: str
    responsavel: str | None
    prazo: NotRequired[str]
    observacao: NotRequired[str | None]

com_responsavel_desconhecido: Tarefa = {
    "titulo": "Revisar relatório",
    "responsavel": None,
}

sem_prazo: Tarefa = {
    "titulo": "Enviar proposta",
    "responsavel": "Lia",
}

com_observacao_nula: Tarefa = {
    "titulo": "Organizar reunião",
    "responsavel": "Caio",
    "observacao": None,
}

Como ler cada anotação

  • responsavel: str | None: a chave é obrigatória; seu valor pode ser texto ou None.
  • prazo: NotRequired[str]: a chave pode estar ausente; se existir, seu valor deve ser texto.
  • observacao: NotRequired[str | None]: a chave pode estar ausente; se existir, aceita texto ou None.

Em sem_prazo, a chave prazo não foi criada. NotRequired não insere a chave e não produz valor padrão.

Consultar com segurança

Atenção

Não acesse uma chave opcional diretamente

tarefa["prazo"] pode lançar KeyError se prazo estiver ausente. O mypy pode aceitar esse acesso por conhecer o tipo do valor quando a chave existe, mas isso não garante sua presença em tempo de execução.

Primeiro a chave; depois o valor

Verifique presença antes de usar um campo NotRequired.

python
def descrever_observacao(tarefa: Tarefa) -> str:
    if "observacao" not in tarefa:
        return "Sem observação registrada"

    observacao = tarefa["observacao"]
    if observacao is None:
        return "Observação ainda não definida"

    return observacao.upper()

Dica

Ordem das verificações

Para NotRequired[str | None], faça dois testes quando precisar usar uma operação de str: primeiro, "campo" in registro; depois, registro["campo"] is not None — diretamente ou guardando o valor em uma variável, como no exemplo.

Escolha o contrato e a verificação

Relacione cada requisito à anotação

Associe cada requisito de campo à declaração apropriada.

Toque em um item e depois no par correspondente.

Qual proteção é necessária?

Para chamar .upper() em tarefa["observacao"], sendo observacao: NotRequired[str | None], o que o código deve garantir antes?

Passo 5 de 7

Interpretar e corrigir diagnósticos do mypy

Use os diagnósticos do mypy para localizar incompatibilidades em registros TypedDict e corrigir o código sem enfraquecer seu contrato.

Diagnósticos apontam o contrato violado

Leia a localização e a regra

Quando o mypy encontra um problema em um TypedDict, ele indica a linha e o contrato que não foi cumprido. Os casos mais comuns são: campo obrigatório ausente, valor com tipo incompatível, chave que não existe no registro e atualização com valor do tipo errado.

O diagnóstico não é um convite para usar Any ou ignorá-lo: ele mostra onde o código deixou de representar o contrato declarado.

Do erro à correção

Cada marcação corresponde a uma regra específica do registro.

Diagrama de um registro de tarefa com cinco pontos destacados: campo obrigatório faltando, valor de texto onde se espera número, chave extra, consulta a chave inexistente e atualização com tipo incompatível; setas ligam os pontos a ícones de diagnóstico.

Localize primeiro a chave e a operação citadas pelo mypy; depois ajuste o dado para que corresponda ao TypedDict.

Programa com incompatibilidades

Crie um arquivo chamado diagnosticos_tarefa.py com este conteúdo. As linhas com comentários ERRO foram deixadas incorretas de propósito.

python
from typing import NotRequired, TypedDict


class Tarefa(TypedDict):
    titulo: str
    prioridade: int
    prazo: NotRequired[str | None]


incompleta: Tarefa = {"titulo": "Estudar TypedDict"}  # ERRO: falta prioridade
valor_invalido: Tarefa = {"titulo": "Estudar", "prioridade": "alta"}  # ERRO
chave_extra: Tarefa = {
    "titulo": "Estudar",
    "prioridade": 2,
    "etiqueta": "python",  # ERRO: chave não declarada
}

print(chave_extra["etiqueta"])  # ERRO: consulta uma chave desconhecida

correta: Tarefa = {"titulo": "Estudar", "prioridade": 2}
correta["prioridade"] = "urgente"  # ERRO: prioridade aceita int

Execute, compare e corrija

Prática no seu computador

No terminal, na pasta do arquivo, execute:

python -m mypy --python-version 3.12 diagnosticos_tarefa.py

Você deve encontrar categorias de diagnóstico equivalentes a estas (a redação exata pode variar conforme a versão):

  • chave obrigatória prioridade ausente;
  • str incompatível com o item prioridade, que espera int;
  • chave extra etiqueta no literal de Tarefa;
  • Tarefa não possui a chave etiqueta;
  • atribuição de str incompatível para prioridade.

Em seguida, substitua todo o conteúdo do arquivo pela versão corrigida abaixo e execute o mesmo comando novamente.

Versão corrigida, com o mesmo contrato

A correção adiciona o campo exigido, usa valores compatíveis, remove a chave não declarada, consulta uma chave conhecida e atualiza prioridade com um inteiro.

python
from typing import NotRequired, TypedDict


class Tarefa(TypedDict):
    titulo: str
    prioridade: int
    prazo: NotRequired[str | None]


incompleta: Tarefa = {
    "titulo": "Estudar TypedDict",
    "prioridade": 1,
}
valor_valido: Tarefa = {"titulo": "Estudar", "prioridade": 2}
sem_chave_extra: Tarefa = {
    "titulo": "Estudar",
    "prioridade": 2,
}

print(sem_chave_extra["titulo"])

correta: Tarefa = {"titulo": "Estudar", "prioridade": 2}
correta["prioridade"] = 3

Relacione mensagens e mudanças

Seu relatório de correção

Após executar as duas versões, relate quais diagnósticos apareceram na primeira e qual alteração resolveu cada um. Inclua o resultado final do mypy em suas palavras.

Escreva pelo menos 180 caracteres (0/180).

Passo 6 de 7

Reconhecer os limites em tempo de execução

Distingua o contrato estático de TypedDict do comportamento real de dicionários durante a execução.

Contrato para o verificador, dict para o Python

O que TypedDict muda — e o que não muda

Um TypedDict descreve para o mypy quais chaves e tipos um registro deve ter. Em tempo de execução, porém, o valor continua sendo um dict comum: a declaração por classe não cria uma instância que fiscaliza campos automaticamente.

Duas responsabilidades diferentes

Compare quem analisa o contrato e quem executa o programa.

Diagrama em duas colunas: um verificador estático compara um contrato TypedDict com um literal de dicionário; o interpretador Python manipula o mesmo valor como um dicionário comum.

O mypy pode apontar incompatibilidades antes da execução; o interpretador não aplica o contrato de TypedDict.

Um diagnóstico não é uma barreira em execução

Experimento local

Salve como limite_typed_dict.py. Primeiro, rode o mypy; depois, execute o arquivo com Python.

python
from typing import TypedDict

class Tarefa(TypedDict):
    titulo: str
    prioridade: int

# O mypy deve diagnosticar: prioridade deveria ser int.
tarefa: Tarefa = {"titulo": "Revisar contrato", "prioridade": "alta"}

print(tarefa)
print(type(tarefa))

Observe os dois resultados

Com python -m mypy limite_typed_dict.py, o mypy informa que o valor de prioridade é incompatível. Ainda assim, python limite_typed_dict.py cria e exibe o dicionário, inclusive mostrando <class 'dict'>. Executar sem erro não prova que os dados obedecem ao TypedDict.

Atenção

Anotação não valida dados recebidos

Anotar um valor vindo de fora como Tarefa não confere suas chaves, não converte valores e não impede conteúdo incompatível em tempo de execução. No próximo tutorial, você verá como estabelecer uma fronteira de validação para dados externos.

Verifique a distinção

Garantias do TypedDict

Se um programa com um registro anotado como TypedDict executa sem erro, então o Python confirmou automaticamente que todas as chaves e valores obedecem ao contrato.

Passo 7 de 7

Aplicação final: modelar e usar um registro

Integre TypedDict, NotRequired, valores None, consultas, atualizações e verificação com mypy em um pequeno programa local.

Contrato da tarefa em um relance

Requisitos do registro

Modele uma tarefa com estes campos:

  • titulo: texto obrigatório;
  • concluida: booleano obrigatório;
  • responsavel: texto ou None, mas a chave é obrigatória;
  • prioridade: inteiro que pode ficar ausente.

A presença da chave e o tipo de seu valor são decisões independentes. Portanto, responsavel usa str | None, enquanto prioridade usa NotRequired[int].

Mapa do contrato

Diagrama de um registro de tarefa dividido em quatro campos: título e concluída como campos obrigatórios, responsável como obrigatório com dois estados de valor possíveis, e prioridade como campo opcional.

Cada campo combina duas informações: se a chave precisa existir e qual tipo de valor ela aceita.

Prática local: programa completo

Monte e verifique

No seu editor, crie um arquivo chamado tarefas.py com o código abaixo. Em um terminal na mesma pasta, execute primeiro python -m mypy tarefas.py e depois python tarefas.py.

Observe separadamente: o mypy confere o contrato anotado; o interpretador executa as operações normais do dicionário.

tarefas.py

python
from typing import NotRequired, TypedDict


class Tarefa(TypedDict):
    titulo: str
    concluida: bool
    responsavel: str | None
    prioridade: NotRequired[int]


def descrever(tarefa: Tarefa) -> str:
    estado = "concluída" if tarefa["concluida"] else "pendente"

    if tarefa["responsavel"] is None:
        pessoa = "sem responsável"
    else:
        pessoa = f"responsável: {tarefa['responsavel']}"

    if "prioridade" in tarefa:
        prioridade = f"prioridade {tarefa['prioridade']}"
    else:
        prioridade = "sem prioridade"

    return f"{tarefa['titulo']}: {estado}; {pessoa}; {prioridade}"


tarefa: Tarefa = {
    "titulo": "Revisar contrato",
    "concluida": False,
    "responsavel": None,
}

tarefa["prioridade"] = 2
tarefa["concluida"] = True

print(descrever(tarefa))

Exemplo

Resultado esperado

O mypy deve terminar sem erros para esse arquivo. A execução deve exibir uma descrição equivalente a:

Revisar contrato: concluída; sem responsável; prioridade 2

Agora adapte apenas os valores do literal inicial: mantenha as quatro regras do contrato e experimente omitir ou incluir prioridade.

Revisão da aplicação

Explique sua solução

Depois de executar sua versão, relate: (1) a declaração de Tarefa; (2) uma consulta ou atualização compatível que você fez; e (3) por que um resultado sem diagnósticos do mypy não transforma dados externos em dados validados.

Escreva pelo menos 140 caracteres (0/140).

Síntese e encerramento

Resumo

O que você consolidou

  • Use TypedDict quando um dicionário representa um registro com chaves conhecidas e tipos específicos por campo.
  • Campos na declaração por classe são obrigatórios, salvo quando marcados com NotRequired.
  • T | None permite o valor None; não torna a chave opcional.
  • Uma chave opcional deve ser testada antes do acesso direto quando sua presença não é garantida.
  • O mypy analisa a compatibilidade estática do código; em execução, um valor de TypedDict é um dict comum e não recebe validação automática.

Tutorial concluído

Parabéns! Você concluiu: Descrever registros com TypedDict

Você concluiu o modelamento de registros com TypedDict. Agora você consegue declarar contratos por chave, representar campos ausentes e valores None de forma independente, usar os campos com precisão e interpretar os limites da verificação estática.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso