Uma anotação como str informa que uma função aceita texto, mas não informa quais textos são válidos. Muitos parâmetros admitem apenas um conjunto fechado de valores, como "json", "csv" ou "xml". typing.Literal permite expressar esses valores exatos na assinatura, melhorando autocomplete, documentação e análise estática.
Neste guia, você aprenderá a usar Literal com strings, números, booleanos e enums, criar overloads, discriminar variantes de TypedDict, validar match/case, definir aliases reutilizáveis e entender por que Literal não substitui a validação em runtime.
O que Literal representa?
Literal descreve um valor específico, não apenas seu tipo geral.
from typing import Literal
Formato = Literal["json", "csv", "xml"]
def exportar(dados: list[dict], formato: Formato) -> bytes:
...
exportar([], "json")
# exportar([], "yaml") # erro no analisador estáticoEm runtime, Formato não bloqueia o valor. A função ainda precisa validar entradas vindas de usuários, arquivos ou rede.
Quando Literal melhora uma API?
Literal é útil quando o conjunto de opções é pequeno, estável e faz parte do contrato público. Ele elimina strings mágicas espalhadas e torna chamadas incorretas visíveis antes da execução.
ModoAbertura = Literal["leitura", "escrita", "anexar"]
def abrir_recurso(caminho: str, modo: ModoAbertura) -> None:
...O editor consegue sugerir os valores permitidos. Isso é mais informativo do que um parâmetro str acompanhado apenas de comentário.
Strings, números e booleanos
Literal aceita valores literais compatíveis com o sistema de typing.
Nivel = Literal[0, 1, 2, 3]
Direcao = Literal["norte", "sul", "leste", "oeste"]
Ativo = Literal[True]
def configurar(nivel: Nivel, direcao: Direcao, ativo: Ativo) -> None:
...Literal[True] é mais específico que bool. Ele pode ser útil em overloads, embora APIs com parâmetros booleanos frequentemente fiquem mais claras com funções separadas ou enums.
Aliases reutilizáveis
Quando uma lista de valores aparece em várias funções, crie um alias.
from typing import Literal, TypeAlias
LogLevel: TypeAlias = Literal["debug", "info", "warning", "error"]
def log(mensagem: str, nivel: LogLevel = "info") -> None:
...Aliases evitam duplicação e facilitam evolução. Alterar o conjunto em um local atualiza todas as assinaturas.
Literal com overload
Um dos usos mais poderosos é relacionar um valor exato ao tipo retornado.
from typing import Literal, overload
@overload
def carregar(caminho: str, *, binario: Literal[True]) -> bytes:
...
@overload
def carregar(caminho: str, *, binario: Literal[False] = False) -> str:
...
def carregar(caminho: str, *, binario: bool = False) -> str | bytes:
modo = "rb" if binario else "r"
with open(caminho, modo) as arquivo:
return arquivo.read()Ao chamar carregar("dados.bin", binario=True), o analisador sabe que o retorno é bytes. Sem Literal, ele precisaria tratar sempre str | bytes.
Variáveis e inferência
Um literal escrito diretamente costuma ser inferido de forma específica em alguns contextos. Já uma variável pode ser ampliada para str.
formato = "json"
exportar([], formato) # alguns verificadores podem inferir str
formato_exato: Formato = "json"
exportar([], formato_exato)Use anotação explícita quando precisar preservar o tipo literal. Final também pode ajudar em constantes.
from typing import Final
FORMATO_PADRAO: Final = "json"Discriminando TypedDict
Literal combina muito bem com TypedDict. Uma chave discriminadora permite que o analisador identifique a variante correta.
from typing import Literal, TypedDict
class EventoCriado(TypedDict):
tipo: Literal["criado"]
id: int
class EventoErro(TypedDict):
tipo: Literal["erro"]
mensagem: str
Evento = EventoCriado | EventoErro
def processar(evento: Evento) -> str:
if evento["tipo"] == "criado":
return f"ID {evento['id']}"
return evento["mensagem"]Dentro de cada ramo, o tipo é refinado automaticamente.
Literal e match/case
Pattern matching fica mais seguro quando o tipo discrimina os casos.
Comando = Literal["iniciar", "parar", "status"]
def executar(comando: Comando) -> str:
match comando:
case "iniciar":
return "iniciado"
case "parar":
return "parado"
case "status":
return "ativo"Alguns analisadores detectam casos impossíveis ou ausência de cobertura, especialmente quando combinados com funções auxiliares que marcam caminhos inalcançáveis.
assert_never e exaustividade
assert_never() ajuda a garantir que todos os valores foram tratados.
from typing import assert_never
def executar(comando: Comando) -> str:
if comando == "iniciar":
return "iniciado"
if comando == "parar":
return "parado"
if comando == "status":
return "ativo"
assert_never(comando)Se um novo valor for adicionado ao alias, o analisador pode apontar o ramo final como alcançável.
Literal com Enum
Para conjuntos pequenos usados apenas como parâmetros, Literal é leve. Quando os valores precisam de métodos, nomes, iteração ou integração rica, Enum pode ser mais apropriado.
from enum import Enum
class FormatoEnum(str, Enum):
JSON = "json"
CSV = "csv"Literal e Enum não são concorrentes absolutos. É possível usar membros de enum em anotações específicas, mas a escolha deve priorizar clareza. O artigo sobre enums em Python aprofunda o tema.
Valores numéricos e sentinelas
Literal pode representar códigos ou sentinelas.
StatusHTTP = Literal[200, 201, 204, 400, 404, 500]
Sentinela = Literal["AUTO", "PADRAO"]Não use Literal para copiar listas enormes de códigos que mudam frequentemente. Nesses casos, um enum, classe de valor ou validação dinâmica pode ser mais sustentável.
LiteralString não é Literal
LiteralString é outro recurso de typing. Ele representa strings construídas a partir de literais confiáveis e foi criado para APIs sensíveis a injeção, como SQL. Não significa uma lista fechada de textos.
from typing import LiteralString
def executar_sql(consulta: LiteralString) -> None:
...Use Literal["a", "b"] para opções exatas e LiteralString para restringir a origem de uma string em verificadores compatíveis.
Validação em runtime
Literal não rejeita valores durante a execução. Uma API pública ainda deve validar.
FORMATOS = {"json", "csv", "xml"}
def exportar_seguro(dados: list[dict], formato: str) -> bytes:
if formato not in FORMATOS:
raise ValueError(f"formato inválido: {formato}")
...Uma estratégia comum é validar a string externa e então passá-la para uma camada interna tipada. Bibliotecas como Pydantic também entendem Literal e podem gerar validação e schema.
APIs públicas e compatibilidade
Adicionar um novo valor a um Literal parece uma mudança compatível, mas consumidores com checagem exaustiva podem precisar de atualização. Remover ou renomear valores é claramente incompatível.
Documente a semântica de cada opção, não apenas o nome. Dois valores podem ter o mesmo tipo, mas efeitos muito diferentes.
Erros comuns
- Usar Literal para dados dinâmicos: ele funciona melhor com conjuntos pequenos e estáveis.
- Confiar nele em runtime: entradas externas ainda precisam de validação.
- Duplicar listas em muitas assinaturas: crie aliases reutilizáveis.
- Combinar dezenas de valores sem necessidade: considere Enum ou classe de configuração.
- Ignorar inferência ampliada: anote variáveis quando o literal precisar ser preservado.
- Usar cast para esconder dados inválidos: cast não valida o valor.
Exemplo completo: cliente de relatório
from typing import Literal, overload
Formato = Literal["texto", "bytes"]
@overload
def gerar_relatorio(*, formato: Literal["texto"]) -> str:
...
@overload
def gerar_relatorio(*, formato: Literal["bytes"]) -> bytes:
...
def gerar_relatorio(*, formato: Formato) -> str | bytes:
conteudo = "resultado"
if formato == "texto":
return conteudo
return conteudo.encode("utf-8")
texto = gerar_relatorio(formato="texto")
binario = gerar_relatorio(formato="bytes")O editor conhece o retorno específico de cada chamada e oferece métodos apropriados sem casts.
Conclusão
typing.Literal expressa opções exatas no sistema de tipos. Ele melhora contratos, autocomplete, overloads, variantes discriminadas e checagem exaustiva.
A documentação oficial de Literal no módulo typing detalha valores aceitos e equivalência. Use Literal para conjuntos fechados e estáveis, mantenha validação em runtime para dados externos e escolha Enum quando o domínio precisar de comportamento próprio.







