Módulo 2 - Tipagem estática

typing: coleções, Optional e Union

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

O que você vai aprender

  • Anotar coleções com list[int], dict[str, int] e tuple.
  • Marcar valores que podem faltar com Optional e X | None.
  • Aceitar mais de um tipo com Union e a sintaxe com barra.
  • Escolher entre a notação antiga do typing e a moderna com minúsculas.

Anotando listas, dicionários e tuplas

Anotar uma coleção não basta dizer que é uma lista; o interessante é dizer do que ela é feita. Você escreve o tipo do contêiner e, entre colchetes, o tipo dos itens. Uma lista de inteiros é list[int]. Um dicionário que mapeia nomes a idades é dict[str, int], porque a chave é texto e o valor é número. Uma tupla com posições de tipos diferentes é tuple[str, int, bool], já que cada posição tem o seu tipo. Se a tupla for uma sequência homogênea de tamanho qualquer, usa-se as reticências: tuple[int, ...].

def somar_todos(numeros: list[int]) -> int:
    return sum(numeros)

def idades_por_nome() -> dict[str, int]:
    return {"Ana": 30, "Beto": 25}

def coordenada() -> tuple[float, float]:
    return (2.5, 4.0)

print(somar_todos([1, 2, 3]))  # 6
print(idades_por_nome())       # {'Ana': 30, 'Beto': 25}
print(coordenada())            # (2.5, 4.0)

list[int], dict[str, int] e tuple[float, float]: o tipo do conteúdo fica explícito.

Essa notação com colchetes, em minúsculas, é a forma moderna e vale a partir do Python 3.9. Antes era preciso importar List, Dict e Tuple com maiúscula do módulo typing. Você ainda vai encontrar código antigo assim, então é bom reconhecer a forma antiga, mas em código novo prefira as minúsculas, que são mais limpas e não exigem import.

Valores que podem faltar ou ter vários tipos

Duas situações aparecem o tempo todo. A primeira: um valor que pode existir ou não. Uma função de busca devolve o item encontrado ou None quando não acha nada. Isso é um Optional: Optional[str] significa texto ou None. A segunda: um valor que pode ser de mais de um tipo. Uma função que aceita um identificador tanto como número quanto como texto usa um Union: Union[int, str]. Desde o Python 3.10 há uma escrita mais curta com a barra vertical: int | None no lugar de Optional[int], e int | str no lugar de Union[int, str].

def buscar_usuario(id: int) -> str | None:
    banco = {1: "Ana", 2: "Beto"}
    return banco.get(id)  # devolve o nome ou None

def normalizar_id(valor: int | str) -> str:
    return str(valor)

nome = buscar_usuario(1)
if nome is not None:
    print(nome.upper())  # ANA

print(buscar_usuario(99))    # None
print(normalizar_id(42))     # 42

str | None marca um valor que pode faltar; int | str aceita dois tipos.

Notação antiga e moderna lado a lado

IntençãoForma antiga (typing)Forma moderna
Lista de inteirosList[int]list[int]
Dicionário texto->númeroDict[str, int]dict[str, int]
Valor opcionalOptional[str]str | None
Vários tiposUnion[int, str]int | str

A forma moderna dispensa import e é a recomendada em código novo.

A tabela resume o essencial. Em código novo, prefira a coluna da direita: tipos embutidos em minúsculas e a barra vertical para opcional e união. Ela é mais curta e não exige importar nada. A coluna do meio ainda aparece em bibliotecas e projetos antigos, então reconheça-a quando ler código dos outros. As duas descrevem exatamente a mesma coisa para o mypy; a diferença é só de escrita e de versão mínima do Python.

Com coleções, opcionais e uniões você já cobre a maioria das anotações do dia a dia. O que falta são os tipos que descrevem comportamento, e não só dados: uma função passada como argumento, um tipo genérico que funciona para qualquer conteúdo, e a ideia de aceitar qualquer objeto que tenha certos métodos. É o assunto das próximas aulas, começando por Callable e Any.

Teste rápido

Uma função de busca devolve o nome encontrado ou None. Qual anotação de retorno é a correta?

Perguntas frequentes

Qual a diferença entre Optional[int] e int | None?
Nenhuma no significado: ambas dizem inteiro ou None. Optional vem do módulo typing e exige import; a barra vertical, disponível a partir do Python 3.10, dispensa import e é mais curta. Em código novo prefira int | None; reconheça Optional ao ler código mais antigo.
Posso escrever list sem dizer o tipo dos itens?
Pode, mas perde valor. Um list cru significa lista de qualquer coisa, e o mypy verifica pouco. Anotar list[int] ou list[str] deixa o mypy conferir se você está colocando e tirando o tipo certo. Sempre que souber o tipo dos itens, informe.
Como anoto uma tupla que sempre tem exatamente três floats?
Com tuple[float, float, float], uma posição para cada float. Se a tupla tiver tamanho variável mas itens do mesmo tipo, use as reticências: tuple[float, ...]. A diferença é que a primeira descreve posições fixas e a segunda uma sequência homogênea de tamanho livre.
Union pode ter mais de dois tipos?
Sim. Union[int, str, float] aceita qualquer um dos três, e a escrita moderna equivalente é int | str | float. Ainda assim, uma união com muitos tipos costuma ser sinal de que o desenho pode melhorar; use com parcimônia e prefira poucos tipos bem escolhidos.
Por que o mypy me obriga a checar None antes de usar o valor?
Porque um valor X | None pode ser None, e chamar um método em None gera erro em execução. Ao exigir um if valor is not None antes do uso, o mypy garante que você tratou o caso vazio. É a proteção contra o famoso erro de NoneType que acontece sem tipos.
Preciso importar algo para usar dict[str, int]?
Não, a partir do Python 3.9. Os tipos embutidos em minúsculas, como list, dict e tuple, aceitam colchetes diretamente, sem import. Só a forma antiga com maiúscula, Dict, List, Tuple, exigia importar do typing. Em versões atuais, escreva em minúsculas e siga.

Fontes

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