TypeGuard no Python: refine tipos com segurança

Publicado em: 28/08/2026
Tempo de leitura: 6 minutos
Close-up of hands typing on a laptop keyboard, Python book in sight, coding in progress.

Em projetos Python com tipagem estática, é comum receber valores amplos e precisar provar ao analisador que eles pertencem a um tipo mais específico. Testes com isinstance() resolvem muitos casos, mas funções auxiliares personalizadas nem sempre refinam o tipo automaticamente. typing.TypeGuard permite declarar que uma função booleana atua como um predicado de tipo: quando ela retorna True, o verificador entende que o argumento corresponde ao tipo indicado.

Neste guia, você aprenderá a criar TypeGuards seguros, validar listas, dicionários e objetos, combinar refinamento com Protocol, TypedDict e Literal, evitar promessas incorretas e decidir quando um simples isinstance() é suficiente.

O problema do refinamento em funções auxiliares

from typing import Any

def eh_lista_de_str(valor: list[Any]) -> bool:
    return all(isinstance(item, str) for item in valor)

dados: list[object] = ["a", "b"]
if eh_lista_de_str(dados):
    primeiro = dados[0]
    # alguns analisadores ainda veem object

A função retorna um booleano correto em runtime, porém sua assinatura não comunica uma relação de tipagem. O analisador sabe apenas que recebeu list[object] e obteve bool.

Declarando um TypeGuard

from typing import TypeGuard

def eh_lista_de_str(valor: list[object]) -> TypeGuard[list[str]]:
    return all(isinstance(item, str) for item in valor)

if eh_lista_de_str(dados):
    primeiro = dados[0]
    print(primeiro.upper())

Dentro do bloco verdadeiro, dados é tratado como list[str]. O TypeGuard aparece no tipo de retorno, mas a função continua devolvendo apenas True ou False em runtime.

TypeGuard é uma promessa

O verificador não executa sua função para confirmar se a lógica é correta. Ele confia na assinatura. Uma implementação defeituosa pode produzir uma falsa sensação de segurança.

def eh_int(valor: object) -> TypeGuard[int]:
    return True  # incorreto

Depois dessa chamada, o analisador permitirá operações de inteiro mesmo quando o valor for uma string, lista ou objeto arbitrário. Trate TypeGuard como uma fronteira de confiança e teste seus predicados cuidadosamente.

Refinando uniões

from dataclasses import dataclass
from typing import TypeGuard

@dataclass
class Sucesso:
    valor: str

@dataclass
class Falha:
    erro: str

Resultado = Sucesso | Falha

def foi_sucesso(resultado: Resultado) -> TypeGuard[Sucesso]:
    return isinstance(resultado, Sucesso)

def processar(resultado: Resultado) -> None:
    if foi_sucesso(resultado):
        print(resultado.valor)
    else:
        print(resultado.erro)

Esse padrão deixa o código chamador expressivo e centraliza a regra de identificação.

Validando TypedDict

Dados de JSON e APIs frequentemente chegam como dict[str, object]. Um TypeGuard pode validar a estrutura antes de permitir acesso tipado.

from typing import TypedDict, TypeGuard

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

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

O predicado precisa validar todas as chaves obrigatórias e seus tipos. Apenas verificar a existência de uma chave não é suficiente quando o TypeGuard promete a estrutura completa.

Campos opcionais e chaves extras

Se o TypedDict possui campos opcionais, valide os campos obrigatórios e, quando os opcionais existirem, confirme seus tipos. Chaves extras podem ser aceitas ou rejeitadas conforme o contrato da aplicação. Documente essa decisão, pois o tipo estático não descreve sozinho todas as políticas de validação de runtime.

TypeGuard com Protocol

Protocol define contratos estruturais. Um TypeGuard pode identificar objetos que oferecem determinados atributos e métodos.

from typing import Protocol, TypeGuard

class Gravavel(Protocol):
    def salvar(self) -> None: ...

def eh_gravavel(valor: object) -> TypeGuard[Gravavel]:
    return callable(getattr(valor, "salvar", None))

Esse teste confirma apenas que existe algo chamável chamado salvar. Ele não verifica a assinatura completa. Para contratos críticos, prefira classes abstratas, validação explícita ou testes adicionais.

Listas homogêneas

from collections.abc import Sequence

def eh_sequencia_de_int(
    valores: Sequence[object],
) -> TypeGuard[Sequence[int]]:
    return all(isinstance(valor, int) for valor in valores)

Usar interfaces somente leitura, como Sequence, reduz riscos relacionados à mutabilidade. Refinar uma lista mutável para um tipo mais estreito pode ser perigoso se outra referência inserir um valor incompatível depois da validação.

Mutabilidade e invariância

list é invariável. Um list[str] não é automaticamente um list[object] para fins de escrita segura, pois alguém poderia inserir um inteiro. TypeGuard permite relações que não seriam aceitas por subtipagem comum, portanto a implementação e o uso devem preservar as invariantes do contêiner.

Quando a função apenas lê os valores, anotar o parâmetro como Sequence[object] costuma ser mais seguro. Se o código pretende modificar a coleção após o refinamento, controle quem possui referências mutáveis.

Filtrando coleções

def eh_string(valor: object) -> TypeGuard[str]:
    return isinstance(valor, str)

itens: list[object] = ["a", 1, "b"]
textos = [item for item in itens if eh_string(item)]

Verificadores modernos conseguem inferir list[str] para a compreensão. Em APIs de ordem superior, como filter(), o resultado pode variar conforme o analisador e os stubs utilizados.

Combinando com Literal

from typing import Literal, TypeGuard

Modo = Literal["leitura", "escrita"]

def eh_modo(valor: str) -> TypeGuard[Modo]:
    return valor in {"leitura", "escrita"}

Isso é útil ao validar opções vindas de linha de comando, variáveis de ambiente ou arquivos de configuração. O guia sobre Literal no Python mostra como valores exatos melhoram overloads e APIs tipadas.

TypeGuard genérico

Alguns predicados podem preservar relações genéricas.

from typing import TypeGuard, TypeVar

T = TypeVar("T")

def sem_none(valores: list[T | None]) -> TypeGuard[list[T]]:
    return all(valor is not None for valor in valores)

Depois da validação, o analisador pode tratar a lista como list[T]. Novamente, a mutabilidade exige cuidado: outra referência ainda pode inserir None.

Refinamento negativo

TypeGuard foi projetado principalmente para o ramo verdadeiro. Dependendo do caso, o ramo else pode não ser refinado de forma precisa. Para refinamento nos dois ramos, versões modernas do Python oferecem TypeIs, que expressa uma relação mais próxima da interseção real entre tipos.

Use TypeGuard quando você precisa de flexibilidade para estreitar para um tipo que não é necessariamente subtipo estrito do parâmetro. Use TypeIs quando o predicado representa uma identificação de subtipo válida em ambos os ramos.

Diferença para cast

from typing import cast

usuario = cast(Usuario, dados)

cast() não valida nada em runtime. Ele apenas instrui o analisador a confiar no programador. TypeGuard combina uma checagem real com refinamento estático. Prefira TypeGuard em fronteiras externas; reserve cast para situações em que a invariável já foi garantida por outro mecanismo.

Diferença para isinstance

Quando o alvo é uma classe concreta ou uma tupla de classes, isinstance() já costuma refinar corretamente. TypeGuard é útil quando a regra envolve estrutura interna, conteúdo de coleções, combinações de atributos ou validações de domínio.

Testando o predicado

Crie testes positivos e negativos, incluindo valores limítrofes, subclasses, coleções vazias, chaves ausentes e tipos parecidos. Para um TypeGuard de dicionário, teste booleanos onde inteiros são esperados, pois bool é subclasse de int em Python. Caso isso seja indesejado, use type(valor) is int.

Erros comuns

  • Retornar True sem validação completa: o tipo prometido pode ser falso.
  • Esquecer mutabilidade: a coleção pode mudar depois da checagem.
  • Usar TypeGuard onde isinstance basta: aumenta a complexidade sem benefício.
  • Validar apenas algumas chaves: um TypedDict incompleto não cumpre o contrato.
  • Confundir com conversão: TypeGuard não transforma o valor.
  • Esperar refinamento perfeito no else: isso depende da relação e do analisador.

Exemplo completo: validação de resposta de API

from typing import TypedDict, TypeGuard

class Produto(TypedDict):
    id: int
    nome: str
    preco: float

def eh_produto(valor: object) -> TypeGuard[Produto]:
    if not isinstance(valor, dict):
        return False
    id_ = valor.get("id")
    nome = valor.get("nome")
    preco = valor.get("preco")
    return (
        type(id_) is int
        and isinstance(nome, str)
        and isinstance(preco, (int, float))
        and not isinstance(preco, bool)
    )

def carregar(payload: object) -> Produto:
    if not eh_produto(payload):
        raise ValueError("produto inválido")
    return payload

A função carregar() devolve um Produto tipado somente depois da validação. Em aplicações reais, considere mensagens de erro detalhadas ou bibliotecas de schemas quando precisar explicar múltiplas falhas.

Boas práticas

Mantenha predicados pequenos, puros e determinísticos. Dê nomes que expressem claramente a condição. Faça a assinatura refletir o conjunto real de entradas. Evite efeitos colaterais durante a validação. Centralize regras de fronteira e combine testes de runtime com mypy, pyright ou outro analisador no pipeline de integração contínua.

Conclusão

typing.TypeGuard conecta validação booleana e refinamento estático. Ele é especialmente útil para dados externos, coleções homogêneas, TypedDict, Protocol e regras de domínio que não podem ser expressas apenas com isinstance().

A documentação oficial de TypeGuard no módulo typing descreve a semântica formal. Use a ferramenta como uma promessa auditável: valide tudo que o tipo declara, teste casos adversos e prefira interfaces imutáveis quando o refinamento envolver coleções.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    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
    A tranquil wooden pathway winds through a vibrant autumn forest, covered in fallen leaves.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk no Python: percorra diretórios

    Aprenda Path.walk no Python para percorrer diretórios, podar pastas, tratar erros, links simbólicos, tamanhos, remoção segura e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    Minimalist hourglass filled with sand symbolizing time and patience, against a soft background.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.timeout no Python: controle prazos

    Aprenda asyncio.timeout no Python para deadlines, timeout_at, reagendamento, TaskGroup, cleanup, retries e cancelamento assíncrono seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    A detailed view of computer programming code on a screen, showcasing software development.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    TaskGroup no Python: concorrência estruturada

    Aprenda asyncio.TaskGroup no Python para concorrência estruturada, resultados, cancelamento, ExceptionGroup, timeouts e tarefas aninhadas.

    Ler mais

    Tempo de leitura: 5 minutos
    28/08/2026
    Black and white close-up of a dictionary page showing the definition of 'virus.'
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    MappingProxyType: dicionário só leitura

    Aprenda MappingProxyType no Python para expor dicionários somente leitura, criar visões dinâmicas, snapshots e proteger invariantes sem cópias.

    Ler mais

    Tempo de leitura: 6 minutos
    28/08/2026