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.