Required e NotRequired: campos opcionais no TypedDict

Publicado em: 29/08/2026
Tempo de leitura: 6 minutos
High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.

typing.Required e typing.NotRequired permitem controlar a presença de cada chave em um TypedDict. Eles resolvem um problema comum: uma estrutura pode ter algumas chaves obrigatórias e outras opcionais, independentemente do valor aceitar None.

Neste guia, você aprenderá a diferença entre presença e nulabilidade, como combinar campos em classes total=True e total=False, como modelar payloads de criação, atualização e resposta, como usar herança, ReadOnly, Unpack e validação de runtime, além dos erros que tornam contratos de dicionário ambíguos.

O que total controla

Por padrão, todas as chaves declaradas em um TypedDict são obrigatórias:

from typing import TypedDict

class Usuario(TypedDict):
    id: int
    nome: str

usuario: Usuario = {"id": 1, "nome": "Ana"}

Se nome estiver ausente, o analisador deve sinalizar erro. Quando usamos total=False, todas as chaves ficam opcionais:

class AtualizacaoUsuario(TypedDict, total=False):
    nome: str
    email: str

Esse modelo é adequado para patches, pois o cliente pode enviar apenas o campo que deseja alterar.

Required em um TypedDict parcial

from typing import Required, TypedDict

class Evento(TypedDict, total=False):
    id: Required[str]
    origem: Required[str]
    detalhes: dict[str, object]

Mesmo com total=False, id e origem precisam existir. detalhes pode estar ausente. Required sobrescreve a regra geral da classe para uma chave específica.

NotRequired em um TypedDict total

from typing import NotRequired, TypedDict

class Produto(TypedDict):
    id: int
    nome: str
    descricao: NotRequired[str]

id e nome são obrigatórios; descricao pode não existir. NotRequired permite manter a classe total sem criar uma base separada apenas para um campo opcional.

Ausente não é igual a None

class Perfil(TypedDict):
    apelido: NotRequired[str]
    biografia: str | None

apelido pode estar ausente. Se existir, deve ser string. biografia precisa existir, mas seu valor pode ser string ou None. Essa distinção é fundamental para JSON, bancos de dados e APIs de patch.

Três estados diferentes

Um campo pode ter três situações: ausente, presente com valor e presente com None. Nem toda API precisa dos três estados, mas quando precisa, modele-os explicitamente.

class AtualizarPerfil(TypedDict, total=False):
    apelido: str
    biografia: str | None

A ausência de biografia significa “não alterar”. O valor None significa “limpar a biografia”. Uma string significa “definir novo conteúdo”.

Verificando chaves opcionais

def aplicar(perfil: dict[str, object], patch: AtualizarPerfil) -> None:
    if "apelido" in patch:
        perfil["apelido"] = patch["apelido"]
    if "biografia" in patch:
        perfil["biografia"] = patch["biografia"]

O teste com in informa ao analisador que uma chave NotRequired está disponível naquele ramo. Acessar diretamente uma chave opcional pode gerar aviso ou risco de KeyError.

get e valores padrão

dict.get() evita KeyError, mas pode misturar ausência e None:

valor = patch.get("biografia")

Se a semântica distingue “não enviado” de “enviado como None”, use in ou uma sentinela:

AUSENTE = object()
valor = patch.get("biografia", AUSENTE)

Modelos separados por operação

Uma API robusta costuma usar tipos diferentes:

class CriarUsuario(TypedDict):
    nome: str
    email: str
    telefone: NotRequired[str]

class AtualizarUsuario(TypedDict, total=False):
    nome: str
    email: str
    telefone: str | None

class UsuarioResposta(TypedDict):
    id: int
    nome: str
    email: str
    telefone: str | None

O modelo de criação exige os dados mínimos. O patch aceita subconjuntos. A resposta garante campos produzidos pelo servidor.

Herança para compartilhar campos

class Identidade(TypedDict):
    id: int

class DadosPublicos(TypedDict, total=False):
    apelido: str
    avatar: str

class UsuarioCompleto(Identidade, DadosPublicos):
    nome: str

Herança pode combinar grupos de chaves com totalidades diferentes. Porém, hierarquias complexas podem ficar difíceis de entender. Prefira poucas camadas e documente a semântica.

Required e ReadOnly

from typing import ReadOnly

class Registro(TypedDict, total=False):
    id: Required[ReadOnly[int]]
    criado_em: Required[ReadOnly[str]]
    nota: str

id e criado_em precisam existir e não devem ser reatribuídos por consumidores. Presença e escrita continuam sendo dimensões independentes. Consulte o guia sobre ReadOnly no Python.

Required em construtores dinâmicos

Ao montar dicionários em etapas, o analisador pode não conseguir provar que todas as chaves obrigatórias foram adicionadas:

dados = {}
dados["id"] = 1
dados["nome"] = "Ana"
# atribuir dados a Usuario pode falhar na análise

Prefira literais completos, factories tipadas ou uma variável anotada desde o início. Casts devem ser usados apenas quando a validação já ocorreu.

Validação de runtime

TypedDict e seus qualificadores não validam dados externos. Um JSON sem chave obrigatória continua sendo um dicionário em runtime. Valide as fronteiras:

def eh_usuario(valor: object) -> bool:
    if not isinstance(valor, dict):
        return False
    return (
        isinstance(valor.get("id"), int)
        and isinstance(valor.get("nome"), str)
    )

Para refinar o tipo depois da validação, use TypeGuard ou TypeIs. O artigo sobre TypeGuard no Python mostra esse padrão.

APIs e OpenAPI

Frameworks podem traduzir Required e NotRequired em campos obrigatórios ou opcionais de um schema. O suporte varia, especialmente com herança e combinações de qualificadores. Confirme o schema gerado e mantenha testes de contrato.

Unpack em **kwargs

from typing import Unpack

class Opcoes(TypedDict, total=False):
    timeout: float
    cache: bool


def executar(**opcoes: Unpack[Opcoes]) -> None:
    ...

Required e NotRequired controlam quais argumentos nomeados são exigidos quando um TypedDict é usado com Unpack. Um guia dedicado sobre typing.Unpack complementa este tema.

Compatibilidade entre TypedDicts

Uma estrutura com uma chave obrigatória nem sempre é compatível com outra que considera a mesma chave opcional. O consumidor do tipo opcional pode remover a chave ou operar de forma incompatível. As regras consideram presença, escrita, tipo do valor e herança.

Campos reservados e sintaxe funcional

Quando uma chave não pode ser escrita como identificador de classe, use a sintaxe funcional:

Cabecalhos = TypedDict(
    "Cabecalhos",
    {
        "content-type": Required[str],
        "x-request-id": NotRequired[str],
    },
)

Essa forma também ajuda quando as chaves são geradas ou contêm hífens.

Introspecção

TypedDicts expõem conjuntos de chaves obrigatórias e opcionais:

print(Produto.__required_keys__)
print(Produto.__optional_keys__)

Frameworks podem usar essas informações, mas referências futuras e avaliação de anotações podem exigir cuidado. Não use introspecção como substituto de validação completa.

total não conta toda a história

O atributo __total__ informa apenas o valor declarado no corpo atual. Uma classe pode ter __total__ == True e ainda possuir chaves NotRequired ou herdadas de uma base parcial. Para descobrir presença real, use os conjuntos de chaves.

Migração de contratos

Transformar uma chave obrigatória em opcional costuma ser compatível para produtores, mas pode afetar consumidores que acessavam sem teste. Tornar uma chave opcional obrigatória pode quebrar todos os payloads antigos. Faça versões de schema, validação gradual ou valores padrão.

Erros comuns

  • Usar Optional para indicar ausência: T | None controla o valor, não a presença.
  • Acessar NotRequired sem verificar: pode ocorrer KeyError.
  • Reutilizar o modelo de resposta como patch: exige campos que não devem ser enviados.
  • Confiar em TypedDict em runtime: dados externos ainda precisam de validação.
  • Criar heranças profundas: a totalidade real fica difícil de enxergar.
  • Usar get quando ausência e None são diferentes: a semântica pode ser perdida.

Exemplo completo: configuração de tarefa

from typing import NotRequired, Required, TypedDict

class Tarefa(TypedDict, total=False):
    nome: Required[str]
    comando: Required[list[str]]
    diretorio: str
    timeout: float
    tentativas: int
    ambiente: dict[str, str]
    descricao: NotRequired[str]


def executar(tarefa: Tarefa) -> None:
    nome = tarefa["nome"]
    comando = tarefa["comando"]
    diretorio = tarefa.get("diretorio", ".")
    timeout = tarefa.get("timeout", 30.0)
    tentativas = tarefa.get("tentativas", 1)
    print(nome, comando, diretorio, timeout, tentativas)

As duas chaves essenciais são Required. As demais podem ser omitidas e receber padrões. O contrato continua legível sem dividir a estrutura em várias classes.

Boas práticas

Modele presença separadamente de nulabilidade. Crie tipos diferentes para criar, atualizar e responder. Prefira in quando ausência tem significado. Valide dados nas fronteiras. Execute mypy, pyright ou outro analisador no CI. Revise schemas gerados por frameworks e mantenha testes com payloads mínimos, completos e inválidos.

Conclusão

Required e NotRequired tornam TypedDicts mais expressivos ao controlar a presença de cada chave. Eles permitem representar payloads reais sem confundir ausência com None e sem forçar todas as chaves a seguir a mesma totalidade.

A documentação oficial de Required e NotRequired no módulo typing detalha as regras. Use os qualificadores para contratos estáticos claros e complemente-os com validação de runtime em qualquer entrada não confiável.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Detailed view of programming code in a dark theme on a computer screen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ReadOnly no Python: proteja campos TypedDict

    Aprenda ReadOnly no Python para marcar campos TypedDict como somente leitura, modelar contratos imutáveis e evitar alterações acidentais.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    A detailed image of a reticulated python showcasing its patterned scales and intricate skin texture.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    TypeAliasType no Python: aliases em runtime

    Aprenda TypeAliasType no Python para criar aliases explícitos, inspecionar tipos em runtime e modelar APIs genéricas com segurança.

    Ler mais

    Tempo de leitura: 7 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    overload no Python: assinaturas precisas

    Aprenda typing.overload no Python para criar assinaturas precisas com Literal, None, genéricos, métodos e retornos dependentes dos argumentos.

    Ler mais

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

    ClassVar no Python: separe classe e instância

    Aprenda ClassVar no Python para separar atributos de classe e instância em dataclasses, registries, caches, herança e contadores.

    Ler mais

    Tempo de leitura: 7 minutos
    29/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Final no Python: proteja constantes e herança

    Aprenda Final e @final no Python para proteger constantes, atributos, métodos e classes, entendendo os limites em runtime.

    Ler mais

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

    Annotated no Python: tipos com metadados

    Aprenda Annotated no Python para adicionar metadados a tipos, criar validação, schemas, unidades e integrações com frameworks.

    Ler mais

    Tempo de leitura: 7 minutos
    29/08/2026