Never no Python: marque código inalcançável

Publicado em: 29/08/2026
Tempo de leitura: 6 minutos
Close-up view of a computer screen displaying code in a software development environment.

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 None

Já 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 += 1

O 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 None

O 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Macro shot capturing detailed patterns of a python in its natural surroundings.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Concatenate no Python: altere parâmetros

    Aprenda Concatenate no Python para adicionar ou ocultar parâmetros em decoradores tipados com ParamSpec, contexto e dependências.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    A close-up view of a person's hand signing a business contract on a desk with a pen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ParamSpec no Python: preserve assinaturas

    Aprenda ParamSpec no Python para preservar assinaturas em decoradores, callbacks, wrappers async e funções de ordem superior.

    Ler mais

    Tempo de leitura: 5 minutos
    28/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

    TypeIs no Python: refine os dois ramos

    Aprenda TypeIs no Python para refinar tipos nos ramos verdadeiro e falso, comparar com TypeGuard e criar predicados seguros.

    Ler mais

    Tempo de leitura: 6 minutos
    28/08/2026
    Close-up of hands typing on a laptop keyboard, Python book in sight, coding in progress.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    TypeGuard no Python: refine tipos com segurança

    Aprenda TypeGuard no Python para refinar tipos, validar coleções, TypedDict e Protocol com segurança estática e checagem real.

    Ler mais

    Tempo de leitura: 6 minutos
    28/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

    typing.Self no Python: retornos fluentes

    Aprenda typing.Self no Python para métodos fluentes, classmethods, builders, clones, Protocol, context managers e retornos que preservam subclasses.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    Yellow block letters spelling 'error' on a vibrant pink background, capturing a playful message.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ExceptionGroup no Python: múltiplos erros

    Aprenda ExceptionGroup no Python para múltiplos erros, except*, grupos aninhados, TaskGroup, filtros, logging e validação em lote.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026