Módulo 2 - Tipagem estática

Generics com TypeVar e Protocol

10 min de leitura · por Cesar Gargiulo, revisado pela equipe ValorFinal e GuardiaSec · Atualizado em 01/07/2026

O que você vai aprender

  • Escrever funções genéricas com TypeVar preservando a relação de tipos.
  • Entender por que um genérico é melhor que Any nessas situações.
  • Definir um Protocol para aceitar objetos por seus métodos.
  • Reconhecer o duck typing tipado que o Protocol traz.

TypeVar: uma função para qualquer tipo

Imagine uma função que devolve o primeiro item de uma lista. Ela funciona para lista de int, de str, de qualquer coisa, e o tipo que sai é o mesmo que entra: se a lista é de str, o primeiro item é str. Anotar isso com Any perderia essa relação, porque Any some com a informação. A solução é um TypeVar, uma variável de tipo. Você declara T = TypeVar('T') e usa T na assinatura. O T não é um tipo fixo; é um espaço reservado que representa o mesmo tipo em toda a função.

from typing import TypeVar

T = TypeVar("T")

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

n = primeiro([10, 20, 30])       # o mypy sabe: n e int
s = primeiro(["a", "b", "c"])    # o mypy sabe: s e str
print(n)  # 10
print(s)  # a

T liga entrada e saída: lista de int devolve int, lista de str devolve str.

A diferença para Any é sutil e importante. Com list[Any] e retorno Any, o mypy não saberia o tipo do que sai, e você perderia autocompletar e verificação depois. Com list[T] e retorno T, o mypy conclui que primeiro de uma lista de inteiros devolve um inteiro. A relação entre entrada e saída fica preservada. Esse é o coração dos generics: código que serve para qualquer tipo sem abrir mão da verificação.

Limitando o TypeVar

Às vezes o tipo não pode ser qualquer um. Uma função que devolve o maior de dois valores só faz sentido para tipos que se comparam. Você pode restringir o TypeVar com bound, que fixa um limite superior, ou com uma lista de tipos permitidos. Com bound=int, o T aceita int e subtipos de int. Com uma restrição a tipos específicos, como TypeVar('N', int, float), o T só pode ser um daqueles listados. Isso mantém a flexibilidade dentro de um limite seguro.

from typing import TypeVar

N = TypeVar("N", int, float)

def maior(a: N, b: N) -> N:
    return a if a > b else b

print(maior(3, 7))        # 7  (int)
print(maior(2.5, 1.5))    # 2.5 (float)
# maior("a", "b") seria apontado pelo mypy: str nao esta na lista

N restrito a int e float: a função aceita esses tipos e nada mais.

Protocol: duck typing verificado

No Intermediário você viu o duck typing: se um objeto tem o método que você precisa, ele serve, não importa a classe dele. O problema é que isso não era verificável antes de rodar. O Protocol resolve. Você declara uma classe que herda de Protocol e lista os métodos e atributos esperados, sem implementação. Qualquer objeto que tenha esses métodos satisfaz o Protocol, mesmo sem herdar dele. O mypy passa a verificar isso: é o duck typing com rede de segurança.

from typing import Protocol

class TemArea(Protocol):
    def area(self) -> float:
        ...

class Circulo:
    def __init__(self, raio: float):
        self.raio = raio
    def area(self) -> float:
        return 3.14159 * self.raio ** 2

class Quadrado:
    def __init__(self, lado: float):
        self.lado = lado
    def area(self) -> float:
        return self.lado ** 2

def maior_area(formas: list[TemArea]) -> float:
    return max(forma.area() for forma in formas)

print(maior_area([Circulo(2), Quadrado(3)]))  # 12.566...

Circulo e Quadrado satisfazem TemArea sem herdar dele: basta ter area().

Nem Circulo nem Quadrado herdam de TemArea, e mesmo assim os dois são aceitos por maior_area, porque ambos têm o método area que devolve float. Isso é poderoso: você define contratos por comportamento, não por hierarquia de classes. É o jeito pythônico de tipar, alinhado com o duck typing, mas agora o mypy confere. Fecha assim o núcleo da tipagem estática; falta ver a ferramenta que roda toda essa verificação, o mypy, na próxima aula.

Teste rápido

Por que Circulo é aceito onde se espera um TemArea, mesmo sem herdar de TemArea?

Perguntas frequentes

Qual a diferença entre usar TypeVar e usar Any na assinatura?
TypeVar preserva a relação entre os tipos: se entra uma lista de str, o mypy sabe que sai str. Any apaga essa informação e você perde autocompletar e verificação depois. Sempre que a saída depender do tipo de entrada, o TypeVar é a escolha certa.
Preciso registrar minhas classes para elas satisfazerem um Protocol?
Não. É a grande vantagem do Protocol: qualquer objeto que tenha os métodos e atributos exigidos satisfaz o contrato automaticamente, sem herança nem registro. O mypy compara a forma do objeto com a do Protocol e aceita se combinar. É duck typing estrutural.
O que significa o bound de um TypeVar?
O bound fixa um limite superior para o tipo. Com TypeVar('T', bound=int), o T pode ser int ou qualquer subtipo de int, mas não str. Isso permite chamar métodos que todo subtipo do bound tem, mantendo a segurança. É útil quando o genérico precisa de certas operações.
Aquele ... dentro do método do Protocol é código de verdade?
É um corpo vazio simbólico. As reticências, ou um pass, indicam que o método não tem implementação no Protocol; ele só declara a assinatura esperada. As implementações reais ficam nas classes concretas que satisfazem o Protocol, como Circulo e Quadrado.
Generics servem só para funções ou também para classes?
Para ambos. Você pode criar classes genéricas que trabalham com um tipo parametrizado, como uma pilha de qualquer tipo. A ideia é a mesma do TypeVar em funções: preservar a relação de tipos. Este módulo foca no caso mais comum, funções genéricas, que já cobre bastante.
Protocol substitui as classes base abstratas?
Resolve muitos dos mesmos problemas de outra forma. A classe base abstrata exige herança explícita; o Protocol verifica por estrutura, sem herança. Cada um tem seu lugar: use Protocol quando quiser aceitar objetos de origens diferentes que apenas compartilham um comportamento.

Fontes

Seu progresso fica salvo neste aparelho. Assinantes sincronizam entre os aparelhos.