O que são Type Hints em Python
Type hints (anotações de tipo) são uma forma de indicar explicitamente qual tipo de dado uma variável, parâmetro de função ou retorno de função deve possuir. Introduzidas na PEP 484 (Python 3.5), elas permitem que você especifique tipos sem alterar o comportamento da linguagem em tempo de execução. Python continua sendo dinamicamente tipado, mas as anotações servem como documentação viva e como base para ferramentas de análise estática.
Quando você escreve nome: str = "João", está dizendo: "a variável nome deve conter uma string". Isso não impede que você atribua um inteiro depois (Python não vai reclamar em runtime), mas comunica sua intenção e permite que ferramentas como mypy detectem problemas potenciais antes do código ser executado. Pense em type hints como um contrato que você estabelece com quem vai usar seu código — inclusive você mesmo, meses depois.
Sintaxe Fundamental e Tipos Básicos
Anotações em Variáveis
A forma mais simples é anotar uma variável com seu tipo esperado. Use a sintaxe nome: tipo antes da atribuição ou logo após:
idade: int = 25
nome: str = "Maria"
altura: float = 1.75
ativo: bool = True
Se você não atribuir um valor imediatamente, a anotação fica como documentação:
email: str
# Pode ser atribuído depois
email = "usuario@example.com"
Anotações em Funções
As anotações em funções especificam os tipos dos parâmetros e do retorno. Use -> para indicar o tipo de retorno:
def calcular_desconto(preco: float, percentual: float) -> float:
return preco * (1 - percentual / 100)
resultado = calcular_desconto(100.0, 10.0)
print(resultado) # 90.0
Quando uma função não retorna nada, use None:
def exibir_mensagem(texto: str) -> None:
print(f"Mensagem: {texto}")
exibir_mensagem("Olá") # Olá
Tipos Primitivos
Os tipos básicos que você já conhece do Python funcionam como anotações:
numero: int
texto: str
decimal: float
booleano: bool
nada: None
O Módulo typing e Tipos Avançados
Containers Tipados
Quando você trabalha com listas, dicionários e outros containers, o módulo typing permite especificar que tipo de elemento eles contêm. Sem typing, apenas list não diz se é uma lista de inteiros ou strings:
from typing import List, Dict, Tuple, Set
numeros: List[int] = [1, 2, 3]
nomes: List[str] = ["Ana", "Bruno", "Carlos"]
configuracoes: Dict[str, int] = {"timeout": 30, "tentativas": 3}
coordenadas: Tuple[float, float] = (10.5, 20.3)
ids_unicos: Set[str] = {"user1", "user2"}
Isso é muito mais preciso do que apenas escrever list. Ferramentas de análise podem agora verificar se você está tentando adicionar uma string a uma lista de inteiros:
def processar_ids(ids: List[str]) -> None:
for id_item in ids:
print(f"ID: {id_item}")
processar_ids(["a", "b", "c"]) # Correto
# processar_ids([1, 2, 3]) # mypy alertaria aqui
Union e Optional
Union permite que uma variável ou retorno tenha múltiplos tipos possíveis. Optional é um atalho para "tipo ou None":
from typing import Union, Optional
def encontrar_usuario(id_usuario: int) -> Optional[str]:
usuarios = {1: "Alice", 2: "Bob"}
return usuarios.get(id_usuario) # Retorna string ou None
resultado = encontrar_usuario(1)
print(resultado) # Alice
resultado = encontrar_usuario(999)
print(resultado) # None
Union é útil quando múltiplos tipos são válidos:
def converter_para_inteiro(valor: Union[str, int]) -> int:
if isinstance(valor, str):
return int(valor)
return valor
print(converter_para_inteiro("42")) # 42
print(converter_para_inteiro(42)) # 42
Generic Types e Callable
Para funções que aceitam outras funções como parâmetro, use Callable:
from typing import Callable
def aplicar_operacao(a: int, b: int, operacao: Callable[[int, int], int]) -> int:
return operacao(a, b)
def somar(x: int, y: int) -> int:
return x + y
resultado = aplicar_operacao(5, 3, somar)
print(resultado) # 8
Aqui, Callable[[int, int], int] significa: "uma função que recebe dois inteiros e retorna um inteiro".
Benefícios Práticos e Ferramentas
Detecção de Erros com mypy
O mypy é um verificador de tipo estático que analisa seu código Python sem executá-lo. Ele encontra incompatibilidades de tipo que poderiam causar bugs:
# arquivo: exemplo.py
def saudacao(nome: str) -> str:
return f"Olá, {nome}!"
resultado = saudacao(123) # Erro: int não é str
Executando mypy exemplo.py, ele detecta o problema:
exemplo.py:4: error: Argument 1 to "saudacao" has incompatible type "int";
expected "str"
Sem type hints, esse erro passaria desapercebido até a execução. Com type hints, você encontra o problema imediatamente durante desenvolvimento.
Melhor Experiência do IDE
IDEs como PyCharm e VS Code usam type hints para oferecer autocompletar mais preciso e inteligente. Quando você digita usuarios., o editor sabe que usuarios é uma List[Dict[str, str]] e sugere apenas métodos e chaves relevantes:
from typing import List, Dict
usuarios: List[Dict[str, str]] = [
{"nome": "Alice", "email": "alice@example.com"},
{"nome": "Bob", "email": "bob@example.com"}
]
# Ao digitar usuarios[0]., o IDE sabe que é um Dict
# e sugere as chaves "nome" e "email"
primeira_usuario = usuarios[0]
print(primeira_usuario["nome"]) # Autocompletar funciona perfeitamente
Documentação Viva
Type hints servem como documentação inline que nunca fica desatualizada:
def buscar_usuario_por_email(email: str) -> Optional[Dict[str, any]]:
"""
Busca um usuário no banco de dados.
Args:
email: O e-mail do usuário a buscar
Returns:
Dicionário com dados do usuário ou None se não encontrado
"""
# implementação
pass
Qualquer desenvolvedor lendo seu código sabe imediatamente o que a função espera e o que ela retorna, sem precisar ler a documentação completa.
Refatoração Segura
Quando você precisa alterar tipos de dados, as anotações ajudam a identificar todos os locais afetados:
# Antes
def calcular_total(precos: List[float]) -> float:
return sum(precos)
# Depois, precisa retornar inteiro também?
def calcular_total(precos: List[float]) -> Tuple[float, int]:
total = sum(precos)
return total, len(precos)
# mypy alertará todos os locais que usam essa função
Conclusão
Type hints em Python não são obrigatórios, mas oferecem benefícios concretos: detecção de erros mais cedo, melhor experiência em IDEs, documentação clara e refatorações mais seguras. Eles são especialmente valiosos em projetos maiores e em times, onde a comunicação clara sobre contratos de dados é fundamental. A chave é começar com tipos simples e ir adoptando tipos mais avançados conforme sua necessidade aumenta — Python flexível permite crescimento gradual sem impor rigidez desde o início.