Um genérico preserva relações entre tipos sem duplicar implementações. Se uma função recebe um valor e devolve o mesmo tipo, TypeVar comunica isso melhor que Any.

Função e classe genéricas

from typing import Generic, TypeVar

T = TypeVar("T")


def primeiro(itens: list[T]) -> T:
    if not itens:
        raise ValueError("a lista não pode estar vazia")
    return itens[0]


class Caixa(Generic[T]):
    def __init__(self, valor: T) -> None:
        self.valor = valor

    def obter(self) -> T:
        return self.valor

Um verificador infere str para primeiro(["a"]) e para Caixa("a").obter(). Em projetos limitados a Python 3.12 ou posterior, a sintaxe def primeiro[T](...) é mais curta; use a forma compatível com as versões declaradas pelo projeto.

Bound e constraints

Um bound aceita subtipos de um limite; constraints restringem a alternativas exatas. Não adicione variância manual sem entender leitura e escrita do tipo. Comece invariável e deixe a necessidade surgir de um caso concreto.

A relação que TypeVar preserva

Compare duas assinaturas:

from typing import Any, TypeVar

T = TypeVar("T")


def identidade_fraca(valor: Any) -> Any:
    return valor


def identidade(valor: T) -> T:
    return valor

Com Any, o verificador deixa praticamente qualquer operação passar no resultado. Com T, ele relaciona o tipo recebido ao devolvido. identidade("python") é str e identidade(42) é int. Isso não cria um tipo novo nem converte valores em execução; fornece uma variável que o analisador resolve em cada chamada.

Uma variável de tipo usada apenas uma vez costuma não acrescentar informação. def registrar(valor: T) -> None geralmente pode receber object, pois não há uma segunda posição relacionada. Use TypeVar quando o mesmo tipo reaparece na saída, em mais de um parâmetro ou no estado de uma classe.

Vários parâmetros e coleções

Genéricos também expressam relações entre chave, valor e retorno:

from collections.abc import Callable, Iterable

Entrada = TypeVar("Entrada")
Saida = TypeVar("Saida")


def transformar(
    itens: Iterable[Entrada],
    funcao: Callable[[Entrada], Saida],
) -> list[Saida]:
    return [funcao(item) for item in itens]

Ao chamar transformar(["1", "20"], int), um checker infere list[int]. A função aceita qualquer iterável, não apenas listas, porque o corpo só precisa iterar. Escolher o protocolo de entrada mais estreito melhora a reutilização sem perder precisão.

Bounds para capacidades compartilhadas

Um limite superior permite qualquer subtipo que satisfaça a base indicada. Imagine objetos com um método de serialização:

from typing import Protocol


class Serializavel(Protocol):
    def para_dict(self) -> dict[str, object]:
        ...


S = TypeVar("S", bound=Serializavel)


def manter_original(item: S) -> S:
    item.para_dict()
    return item

O corpo pode chamar para_dict, e o retorno mantém o tipo concreto de item. Se a assinatura retornasse apenas Serializavel, essa informação seria perdida. Um bound expressa uma capacidade mínima, não uma conversão.

Constraints têm outra semântica: Texto = TypeVar("Texto", str, bytes) aceita essas famílias e resolve o resultado para uma das alternativas. Um subtipo específico de str é promovido para str. Use constraints quando a implementação realmente aceita um conjunto fechado e trata cada alternativa de modo coerente. Para uma capacidade aberta, prefira bound ou Protocol.

Classes genéricas e encapsulamento

Uma classe genérica mantém o parâmetro entre operações:

class Pilha(Generic[T]):
    def __init__(self) -> None:
        self._itens: list[T] = []

    def adicionar(self, item: T) -> None:
        self._itens.append(item)

    def remover(self) -> T:
        if not self._itens:
            raise IndexError("pilha vazia")
        return self._itens.pop()

Pilha[str] aceita strings e devolve str. A parametrização melhora autocomplete e detecta usos incoerentes antes da execução. Em runtime, contudo, ela não impede pilha.adicionar(3). Se dados vêm de JSON, formulário ou rede, valide-os na fronteira com código específico.

Variância sem mistério

Um contêiner que lê e escreve T normalmente é invariável. Embora Cachorro seja subtipo de Animal, permitir tratar Pilha[Cachorro] como Pilha[Animal] seria inseguro: alguém poderia adicionar um Gato. Uma interface somente de leitura pode ser covariante; um consumidor somente de entrada pode ser contravariante.

Não marque variância para silenciar um erro. Observe as operações públicas e confirme se o parâmetro aparece em posições de entrada, saída ou ambas. Nas sintaxes modernas, verificadores podem inferir variância para parâmetros de tipo de classes; confirme o suporte das versões e ferramentas do projeto.

Sintaxe de Python 3.12

PEP 695 introduziu parâmetros de tipo entre colchetes:

def primeiro[T](itens: list[T]) -> T:
    return itens[0]


class Caixa[T]:
    def __init__(self, valor: T) -> None:
        self.valor = valor

Essa forma reduz declarações globais, mas o arquivo nem sequer é analisado por interpretadores antigos. Bibliotecas devem seguir sua versão mínima suportada. Não misture estilos sem motivo, e confira a configuração do mypy, Pyright ou outra ferramenta usada no projeto.

Armadilhas e critérios práticos

Evite Any como saída de uma função genérica, pois ele quebra a relação que você queria preservar. Evite também retornar T sem receber ou construir T de maneira segura; o checker não tem como adivinhar qual valor concreto criar. Casts podem ocultar um erro de design e devem ser uma exceção documentada.

Comece pela API concreta e escreva exemplos de chamadas esperadas. Se dois tipos precisam permanecer iguais ou um retorno depende do callable recebido, um genérico provavelmente ajuda. Se a função apenas aceita valores heterogêneos e devolve informação independente, object, uma união ou um protocolo pode ser mais honesto.

Inferência, tipos explícitos e depuração

Na maioria das chamadas, deixe o verificador inferir os parâmetros. Escreva caixa: Caixa[str] = Caixa("texto") quando a anotação documentar uma fronteira, resolver uma coleção inicialmente vazia ou impedir uma inferência ampla demais. Repetir tipos óbvios em toda variável aumenta ruído.

Quando o checker produz um resultado inesperado, reduza o exemplo e revele o tipo inferido com o recurso da ferramenta, como reveal_type durante a análise. Confira cada ocorrência de T: parâmetros podem impor requisitos incompatíveis e fazer o analisador procurar um ancestral comum. Por exemplo, uma função que recebe dois valores do mesmo T não garante que ambos tenham exatamente a mesma classe em runtime; o checker pode escolher um tipo comum válido.

Aliases genéricos tornam estruturas longas legíveis. Em Python 3.12, type Resultado[T] = tuple[T, str | None] cria um alias parametrizado. Em versões anteriores, use os recursos compatíveis de typing. Um alias nomeia uma forma de dados, mas não cria validação nem um novo tipo nominal.

Execute o verificador no CI com uma configuração versionada. Gradualmente endureça regras, em vez de cobrir erros com Any. Tipagem genérica entrega valor quando seus contratos são testados continuamente e permanecem compreensíveis para quem mantém a API.

Revise type hints em Python e mypy para executar a análise. A documentação oficial de typing, consultada em 22 de julho de 2026, detalha TypeVar, genéricos e variância. Use genéricos para relações reais, não para tornar uma API simples abstrata demais.