Tipagem estática: type hints básicos
1. Introdução aos Type Hints
Python sempre foi conhecido por sua tipagem dinâmica — você pode atribuir qualquer valor a qualquer variável sem declarações de tipo. No entanto, isso pode tornar o código difícil de entender e manter em projetos grandes. Type hints (ou anotações de tipo) são uma maneira de indicar os tipos esperados de variáveis, parâmetros e retornos de funções, sem alterar o comportamento em tempo de execução.
Os benefícios incluem:
- Legibilidade: outros desenvolvedores (e você no futuro) entendem imediatamente o que uma função espera e retorna.
- Manutenção: ferramentas detectam inconsistências de tipo antes mesmo de executar o código.
- Ferramentas: IDEs oferecem autocompletar, refatoração e detecção de erros mais precisas.
É importante entender que type hints são dicas, não imposições. Python continua sendo uma linguagem dinâmica — a verificação real é feita por ferramentas externas como mypy.
Historicamente, a PEP 484 (2014) introduziu a sintaxe formal de type hints, e desde Python 3.5 eles são suportados oficialmente. A partir do Python 3.9, a sintaxe foi simplificada com tipos nativos como list[int] em vez de List[int] do módulo typing.
2. Sintaxe Básica de Anotações de Tipo
A sintaxe mais simples é anotar variáveis com : seguido do tipo:
nome: str = "Alice"
idade: int = 30
altura: float = 1.75
ativo: bool = True
valor_nulo: None = None
Para funções, anotamos parâmetros e o retorno com ->:
def saudacao(nome: str) -> str:
return f"Olá, {nome}!"
def soma(a: int, b: int) -> int:
return a + b
def processar(valor: float) -> None:
print(f"Processando {valor}")
Os tipos nativos disponíveis são: int, float, str, bool, None, bytes, complex.
3. Tipos de Coleções
Coleções podem ser anotadas com os tipos genéricos. Em Python 3.9+, use a sintaxe nativa:
# Listas
numeros: list[int] = [1, 2, 3]
matriz: list[list[int]] = [[1, 2], [3, 4]]
# Tuplas
coordenada: tuple[float, float] = (10.5, 20.3)
dados_mistos: tuple[str, int, bool] = ("abc", 42, True)
# Dicionários
notas: dict[str, float] = {"João": 8.5, "Maria": 9.0}
config: dict[str, dict[str, int]] = {"tela": {"largura": 1920, "altura": 1080}}
# Conjuntos
tags: set[str] = {"python", "tipagem", "hints"}
imutavel: frozenset[int] = frozenset([1, 2, 3])
Em Python 3.8 e anteriores, você precisa importar List, Dict, etc. do módulo typing:
from typing import List, Dict, Tuple
numeros: List[int] = [1, 2, 3]
notas: Dict[str, float] = {"João": 8.5}
A diferença é apenas sintática — ambas funcionam, mas a sintaxe nativa é mais limpa e recomendada para Python 3.9+.
4. Tipos Opcionais e Union
Muitas vezes, uma variável pode ser de um tipo ou None. Use Optional ou Union:
from typing import Optional, Union
# Optional[str] significa str ou None
def buscar_usuario(id: int) -> Optional[str]:
if id == 1:
return "Alice"
return None
# Union permite múltiplos tipos
def converter(valor: Union[int, float, str]) -> float:
if isinstance(valor, str):
return float(valor)
return float(valor)
# Sintaxe moderna (Python 3.10+): X | Y
def processar(valor: int | float | None) -> None:
if valor is not None:
print(valor * 2)
Boas práticas:
- Use Optional[X] quando o único tipo alternativo for None.
- Use Union[X, Y, Z] para múltiplos tipos possíveis.
- A partir do Python 3.10, X | Y | None é mais legível que Union[X, Y, None].
5. Tipos Especiais: Any, Callable, TypeVar Básico
Any é o tipo coringa — aceita qualquer valor e desativa a verificação:
from typing import Any
def log(mensagem: Any) -> None:
print(mensagem)
dado: Any = 42
dado = "texto" # Sem erro
Callable anota funções como parâmetros:
from typing import Callable
def executar(funcao: Callable[[int, int], int], a: int, b: int) -> int:
return funcao(a, b)
def somar(x: int, y: int) -> int:
return x + y
resultado = executar(somar, 3, 4) # 7
TypeVar permite criar funções genéricas:
from typing import TypeVar
T = TypeVar('T') # Pode ser qualquer tipo
def primeiro_elemento(lista: list[T]) -> T:
return lista[0]
print(primeiro_elemento([1, 2, 3])) # int
print(primeiro_elemento(["a", "b"])) # str
NoReturn é para funções que nunca retornam (ex.: que sempre levantam exceção):
from typing import NoReturn
def erro_fatal() -> NoReturn:
raise SystemExit("Erro crítico")
6. Type Hints com Classes e Objetos
Anote atributos de classe e métodos com self:
class Pessoa:
nome: str
idade: int
def __init__(self, nome: str, idade: int) -> None:
self.nome = nome
self.idade = idade
def aniversario(self) -> None:
self.idade += 1
def apresentar(self) -> str:
return f"Olá, sou {self.nome} e tenho {self.idade} anos"
Em Python 3.11+, use Self para indicar que um método retorna a própria instância:
from typing import Self
class Animal:
def __init__(self, nome: str) -> None:
self.nome = nome
def clone(self) -> Self: # Retorna o mesmo tipo da subclasse
return type(self)(self.nome)
class Cachorro(Animal):
def latir(self) -> str:
return "Au au!"
rex = Cachorro("Rex")
clone_rex = rex.clone() # type: Cachorro
print(clone_rex.latir()) # Funciona!
7. Ferramentas e Verificação Estática
O mypy é o verificador de tipos mais popular. Instale com:
pip install mypy
Execute sobre seu código:
mypy meu_arquivo.py
Exemplo de erros detectados:
def dobro(x: int) -> int:
return x * 2
resultado = dobro("texto") # mypy detecta erro: Argument 1 to "dobro" has incompatible type "str"; expected "int"
Para ignorar uma linha específica:
valor: int = "42" # type: ignore
Configure o mypy com um arquivo mypy.ini:
[mypy]
python_version = 3.11
strict = True
Ou no pyproject.toml:
[tool.mypy]
python_version = "3.11"
strict = true
8. Boas Práticas e Armadilhas Comuns
Quando anotar:
- Sempre anote funções públicas e APIs.
- Anote variáveis complexas ou quando o tipo não é óbvio.
- Evite anotar variáveis locais triviais (for i in range(10) não precisa de i: int).
Evite Any em excesso — ele desativa a verificação e anula os benefícios:
# Ruim
def processar(dados: Any) -> Any:
return dados.upper() # Pode falhar em runtime
# Bom
def processar(dados: str) -> str:
return dados.upper()
Cuidado com tipos mutáveis em parâmetros padrão:
# Ruim - mypy não detecta, mas é perigoso
def adicionar(item: str, lista: list[str] = []) -> list[str]:
lista.append(item)
return lista
# Bom
def adicionar(item: str, lista: list[str] | None = None) -> list[str]:
if lista is None:
lista = []
lista.append(item)
return lista
Documentação vs type hints: eles são complementares. Type hints dizem o que uma função espera/retorna; a docstring explica como e por quê.
def calcular_media(notas: list[float]) -> float:
"""
Calcula a média aritmética de uma lista de notas.
Args:
notas: Lista de valores numéricos (0 a 10).
Returns:
Média calculada.
"""
return sum(notas) / len(notas)
Lembre-se: type hints são uma ferramenta poderosa para tornar seu Python mais robusto, legível e fácil de manter. Comece anotando funções críticas e evolua gradualmente.
Referências
- PEP 484 – Type Hints — Documentação oficial que define a sintaxe e semântica dos type hints em Python.
- Documentação oficial do módulo typing — Referência completa de todos os tipos disponíveis no módulo
typing. - Mypy: Getting Started — Tutorial oficial do mypy, o verificador de tipos estático mais usado para Python.
- Python Type Checking (Real Python) — Guia prático e abrangente sobre type hints, mypy e boas práticas.
- PEP 604 – Complementary syntax for Union types — Documentação da sintaxe
X | Yintroduzida no Python 3.10 para Unions. - Python 3.11: Self Type — Documentação oficial do tipo
Selfpara métodos que retornam a própria instância.