Algumas funções nunca devolvem um valor ao chamador: elas sempre lançam uma exceção, encerram o processo ou entram em um fluxo que não termina. Outras partes do programa deveriam ser impossíveis quando todos os casos de uma união foram tratados. typing.Never representa o tipo vazio, sem valores possíveis, e permite que analisadores entendam esses cenários.
Neste guia, você aprenderá a anotar funções que não retornam, usar assert_never() para verificar exaustividade, combinar Never com Literal, Enum, match/case e callbacks, comparar Never com NoReturn e evitar usos que escondem erros reais.
O tipo sem valores
Tipos comuns descrevem conjuntos de valores. int inclui inteiros, str inclui strings e int | str inclui ambos. Never descreve um conjunto vazio: não existe valor Python válido que possua esse tipo de forma normal.
Por isso, Never também é chamado de tipo inferior ou bottom type. Ele é subtipo de todos os tipos, pois um conjunto vazio está contido em qualquer conjunto.
Função que sempre lança exceção
from typing import Never
def falhar(mensagem: str) -> Never:
raise RuntimeError(mensagem)A anotação informa que o fluxo nunca continua depois da chamada. O analisador pode considerar inalcançável o código seguinte.
usuario = buscar_usuario()
if usuario is None:
falhar("usuário não encontrado")
# aqui, usuario já não é None
print(usuario.nome)A função auxilia o refinamento porque o ramo com valor ausente termina obrigatoriamente.
Encerrando o processo
import sys
from typing import Never
def encerrar(codigo: int, mensagem: str) -> Never:
print(mensagem, file=sys.stderr)
raise SystemExit(codigo)SystemExit é uma exceção, portanto a função não retorna normalmente. O mesmo vale para helpers que sempre chamam outra função anotada como Never.
Never versus None
None é um valor real. Uma função anotada com -> None retorna normalmente, ainda que não produza um resultado útil.
def registrar(texto: str) -> None:
print(texto)
# retorna NoneJá uma função -> Never não chega ao ponto de retorno normal. Confundir os dois tipos faz o contrato mentir.
Never versus NoReturn
typing.NoReturn foi introduzido para anotar funções que nunca retornam. Never generaliza a ideia do tipo vazio e pode aparecer em mais contextos. Para funções, os dois expressam essencialmente o mesmo contrato em analisadores modernos.
from typing import NoReturn
def abortar() -> NoReturn:
raise SystemExit(1)Projetos novos podem preferir Never pela semântica geral, enquanto bibliotecas que suportam versões antigas podem manter NoReturn ou importar Never de typing_extensions.
Verificando exaustividade com assert_never
from typing import Literal, assert_never
Estado = Literal["novo", "pago", "enviado"]
def rotulo(estado: Estado) -> str:
match estado:
case "novo":
return "Novo"
case "pago":
return "Pago"
case "enviado":
return "Enviado"
case _:
assert_never(estado)Se todos os valores de Estado foram tratados, o analisador entende que, no caso padrão, estado possui tipo Never. Se um novo Literal for adicionado e não houver um case correspondente, a chamada a assert_never() gera erro de tipagem.
Por que não usar apenas assert False
case _:
assert False, "estado impossível"Isso falha em runtime, mas não necessariamente força o analisador a provar que o caso é inalcançável. assert_never() conecta a verificação estática e uma falha de runtime caso a invariável seja quebrada.
Exaustividade com if/elif
def cor(estado: Estado) -> str:
if estado == "novo":
return "cinza"
elif estado == "pago":
return "azul"
elif estado == "enviado":
return "verde"
else:
assert_never(estado)O padrão funciona sem match/case. O importante é que o analisador refine progressivamente a união.
Never com Enum
from enum import Enum, auto
from typing import assert_never
class Papel(Enum):
ADMIN = auto()
EDITOR = auto()
LEITOR = auto()
def permissoes(papel: Papel) -> set[str]:
match papel:
case Papel.ADMIN:
return {"ler", "editar", "excluir"}
case Papel.EDITOR:
return {"ler", "editar"}
case Papel.LEITOR:
return {"ler"}
case _:
assert_never(papel)Quando um novo membro é adicionado, o checker pode indicar que a função não é mais exaustiva.
Uniões de classes
from dataclasses import dataclass
@dataclass
class Texto:
valor: str
@dataclass
class Numero:
valor: float
No = Texto | Numero
def renderizar(no: No) -> str:
if isinstance(no, Texto):
return no.valor
if isinstance(no, Numero):
return str(no.valor)
assert_never(no)O último ponto deve ser impossível enquanto No contiver apenas as duas classes.
Never em parâmetros
Um parâmetro anotado como Never declara que a função não pode ser chamada com um valor normal.
def impossivel(valor: Never) -> str:
return "não deveria executar"Esse padrão aparece em helpers de exaustividade e APIs genéricas avançadas. Não é comum em código de aplicação fora desses casos.
Never em genéricos
Inferência de tipos pode produzir Never quando nenhuma alternativa é possível ou uma coleção é conhecida como vazia sem tipo útil. O comportamento exato varia entre analisadores. Em APIs públicas, forneça anotações explícitas quando a inferência de vazio puder confundir o consumidor.
Callbacks que nunca retornam
from collections.abc import Callable
def executar_ou_abortar(
operacao: Callable[[], int],
abortar: Callable[[Exception], Never],
) -> int:
try:
return operacao()
except Exception as erro:
abortar(erro)Como o callback de aborto não retorna, a função externa não precisa de um retorno adicional no bloco de exceção.
Loops infinitos
def servidor() -> Never:
while True:
atender_proxima_requisicao()Uma função com loop comprovadamente infinito pode ser anotada como Never. Porém, se existe break, retorno condicional ou possibilidade de o loop terminar, a anotação pode estar errada.
Geradores não são funções Never
Um gerador pode produzir valores indefinidamente, mas chamar a função retorna imediatamente um objeto gerador. Portanto, a função geradora não deve ser anotada com -> Never.
from collections.abc import Iterator
def contagem() -> Iterator[int]:
numero = 0
while True:
yield numero
numero += 1O iterador pode ser infinito, mas a criação do iterador retorna normalmente.
Função que às vezes falha
def carregar(caminho: str) -> bytes:
if not existe(caminho):
raise FileNotFoundError(caminho)
return ler(caminho)Essa função retorna bytes em alguns caminhos, portanto seu tipo é bytes, não bytes | Never. Never é absorvido por uniões: adicionar um tipo vazio não acrescenta valores possíveis.
Never em overloads
from typing import Literal, overload
@overload
def converter(valor: str, *, estrito: Literal[True]) -> int: ...
@overload
def converter(valor: str, *, estrito: Literal[False]) -> int | None: ...
def converter(valor: str, *, estrito: bool) -> int | None:
try:
return int(valor)
except ValueError:
if estrito:
falhar("inteiro inválido")
return NoneO helper Never permite ao analisador entender que o ramo estrito não produz None após a falha.
Exaustividade e evolução da API
O principal valor de assert_never() aparece quando tipos evoluem. Ao adicionar uma variante a uma união, Enum ou Literal, funções exaustivas passam a falhar no CI até que o novo caso seja tratado. Isso reduz defaults silenciosos que escondem regras de negócio ausentes.
Quando um case padrão é desejável
Em dados externos e não confiáveis, valores desconhecidos são possíveis mesmo que a anotação diga o contrário. Nesse caso, uma estratégia de fallback, erro de validação ou log pode ser mais apropriada que assert_never. Use exaustividade em valores já validados e controlados pelo programa.
Runtime de assert_never
Se for chamado, assert_never() lança uma exceção, pois recebeu um valor que deveria ser impossível. Isso é útil como defesa, mas não substitui validação na fronteira. A principal finalidade continua sendo fazer o analisador verificar o tipo do argumento.
Compatibilidade de versões
Never e assert_never estão disponíveis no módulo typing de versões modernas. Para versões anteriores, use typing_extensions.Never e typing_extensions.assert_never.
Erros comuns
- Anotar como Never uma função que retorna None: são contratos diferentes.
- Usar Never em geradores infinitos: a chamada retorna um iterador.
- Esconder um fallback real: dados externos podem ter variantes desconhecidas.
- Chamar assert_never sem refinar a união: o analisador mostra corretamente que ainda existem casos.
- Usar uma implementação que pode retornar: a anotação se torna falsa.
- Confundir NoReturn com ausência de valor útil: NoReturn/Never significa ausência de retorno normal.
Exemplo completo: comandos exaustivos
from dataclasses import dataclass
from typing import assert_never
@dataclass
class Criar:
nome: str
@dataclass
class Renomear:
id: int
nome: str
@dataclass
class Excluir:
id: int
Comando = Criar | Renomear | Excluir
def executar(comando: Comando) -> None:
match comando:
case Criar(nome=nome):
criar(nome)
case Renomear(id=id_, nome=nome):
renomear(id_, nome)
case Excluir(id=id_):
excluir(id_)
case _:
assert_never(comando)Adicionar uma nova classe à união exige atualizar o dispatcher. A falha ocorre durante análise estática, antes de a nova variante alcançar produção.
Boas práticas
Use Never em helpers pequenos que realmente interrompem o fluxo. Use assert_never após refinamento completo, não como substituto de validação. Execute um checker no CI e trate alertas de exaustividade como mudanças obrigatórias de regra de negócio.
Conclusão
typing.Never representa a ausência total de valores e ajuda a descrever funções que não retornam, callbacks abortivos e caminhos inalcançáveis. Com assert_never(), ele transforma uniões e Enums em contratos exaustivos que evoluem com segurança.
A documentação oficial de Never no Python e de assert_never detalha o comportamento. Use o tipo apenas quando não existe caminho de retorno normal e reserve fallbacks para dados realmente abertos ou não validados.







