Tipagem Estatica em Python com Mypy
Aprenda a usar tipagem estatica em Python com mypy. Descubra como type hints tornam seu codigo mais seguro, legivel e facil de manter.
Python sempre foi uma linguagem de tipagem dinamica, mas desde a versao 3.5, com a PEP 484, passou a suportar type hints. Combinados com ferramentas como o mypy, esses hints transformam a experiencia de desenvolvimento, detectando erros antes mesmo de executar o codigo. Neste artigo, vamos explorar como usar tipagem estatica em Python de forma pratica e eficiente.
O Que Sao Type Hints
Type hints sao anotacoes que indicam os tipos esperados de variaveis, parametros e retornos de funcoes. Eles nao alteram o comportamento do programa em tempo de execucao, mas servem como documentacao viva e permitem a analise estatica.
# Sem type hints
def calcular_desconto(preco, percentual):
return preco * (1 - percentual / 100)
# Com type hints
def calcular_desconto(preco: float, percentual: float) -> float:
return preco * (1 - percentual / 100)
A segunda versao deixa claro que a funcao recebe dois floats e retorna um float. Qualquer desenvolvedor que leia esse codigo entende imediatamente o contrato da funcao.
Instalando e Configurando o Mypy
O mypy e o verificador de tipos mais popular para Python. A instalacao e simples:
pip install mypy
Para verificar um arquivo, basta executar:
mypy meu_arquivo.py
Para projetos maiores, crie um arquivo mypy.ini ou adicione configuracoes no pyproject.toml:
[tool.mypy]
python_version = "3.12"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true
strict_optional = true
Com disallow_untyped_defs = true, o mypy exigira que todas as funcoes tenham anotacoes de tipo, garantindo cobertura completa.
Tipos Basicos e Compostos
Python oferece uma variedade de tipos para anotacoes. Os mais comuns sao:
# Tipos primitivos
nome: str = "Maria"
idade: int = 28
salario: float = 5500.00
ativo: bool = True
# Listas e dicionarios (Python 3.9+)
nomes: list[str] = ["Ana", "Carlos", "Bruno"]
notas: dict[str, float] = {"matematica": 8.5, "portugues": 9.0}
# Tuplas
coordenada: tuple[float, float] = (23.5505, 46.6333)
# Conjuntos
tags: set[str] = {"python", "programacao", "backend"}
Para versoes anteriores ao Python 3.9, use os tipos do modulo typing:
from typing import List, Dict, Tuple, Set
nomes: List[str] = ["Ana", "Carlos"]
notas: Dict[str, float] = {"matematica": 8.5}
Tipos Opcionais e Union
Muitas vezes uma variavel pode ter mais de um tipo ou ser None. Para isso, usamos Optional e Union:
from typing import Optional, Union
def buscar_usuario(user_id: int) -> Optional[dict]:
"""Retorna o usuario ou None se nao encontrado."""
usuarios = {1: {"nome": "Ana", "email": "ana@email.com"}}
return usuarios.get(user_id)
def formatar_valor(valor: Union[int, float]) -> str:
"""Aceita int ou float e retorna string formatada."""
return f"R$ {valor:,.2f}"
# Python 3.10+ permite a sintaxe com pipe
def buscar_usuario_novo(user_id: int) -> dict | None:
usuarios = {1: {"nome": "Ana"}}
return usuarios.get(user_id)
O mypy vai alertar se voce tentar acessar atributos do resultado de buscar_usuario sem antes verificar se ele nao e None.
TypedDict e Dataclasses Tipadas
Para dicionarios com estrutura fixa, TypedDict e extremamente util:
from typing import TypedDict
class Usuario(TypedDict):
nome: str
email: str
idade: int
ativo: bool
def criar_usuario(nome: str, email: str, idade: int) -> Usuario:
return {
"nome": nome,
"email": email,
"idade": idade,
"ativo": True,
}
usuario = criar_usuario("Carlos", "carlos@email.com", 30)
print(usuario["nome"]) # OK
# print(usuario["telefone"]) # mypy: erro! chave nao existe
Dataclasses com tipagem oferecem uma alternativa ainda mais robusta:
from dataclasses import dataclass
@dataclass
class Produto:
nome: str
preco: float
estoque: int = 0
def aplicar_desconto(self, percentual: float) -> float:
return self.preco * (1 - percentual / 100)
produto = Produto(nome="Notebook", preco=3500.00, estoque=10)
valor_final = produto.aplicar_desconto(15)
print(f"Preco com desconto: R$ {valor_final:.2f}")
Generics e Protocolos
Para funcoes que operam com diferentes tipos, use generics:
from typing import TypeVar, Sequence
T = TypeVar("T")
def primeiro_elemento(sequencia: Sequence[T]) -> T:
"""Retorna o primeiro elemento de qualquer sequencia."""
if not sequencia:
raise ValueError("Sequencia vazia")
return sequencia[0]
# mypy infere o tipo de retorno automaticamente
numero = primeiro_elemento([1, 2, 3]) # int
texto = primeiro_elemento(["a", "b", "c"]) # str
Protocolos permitem tipagem estrutural, semelhante a duck typing com verificacao estatica:
from typing import Protocol
class Enviavel(Protocol):
def enviar(self, mensagem: str) -> bool: ...
class EmailService:
def enviar(self, mensagem: str) -> bool:
print(f"Email enviado: {mensagem}")
return True
class SMSService:
def enviar(self, mensagem: str) -> bool:
print(f"SMS enviado: {mensagem}")
return True
def notificar(servico: Enviavel, mensagem: str) -> None:
sucesso = servico.enviar(mensagem)
if sucesso:
print("Notificacao enviada com sucesso")
notificar(EmailService(), "Bem-vindo!")
notificar(SMSService(), "Codigo: 1234")
Integrando Mypy no Fluxo de Trabalho
A melhor forma de aproveitar o mypy e integra-lo ao seu pipeline de desenvolvimento. Adicione-o como pre-commit hook:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
Tambem e possivel integra-lo ao CI/CD no GitHub Actions:
# .github/workflows/typecheck.yml
name: Type Check
on: [push, pull_request]
jobs:
mypy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install mypy
- run: mypy src/
Boas Praticas de Tipagem
Adotar tipagem estatica de forma eficiente requer alguns cuidados. Primeiro, comece pelos modulos mais criticos do seu projeto, como a camada de dados e as interfaces publicas da API. Nao tente tipar tudo de uma vez em um projeto existente.
Segundo, use reveal_type() durante o desenvolvimento para entender como o mypy infere os tipos:
x = [1, 2, 3]
reveal_type(x) # mypy revela: list[int]
Terceiro, evite o uso excessivo de Any. Ele desativa a verificacao de tipos e anula os beneficios do mypy. Se precisar usar Any, documente o motivo.
Por fim, mantenha as dependencias de tipos atualizadas. Pacotes como types-requests, types-redis e types-PyYAML fornecem stubs de tipos para bibliotecas populares que ainda nao possuem anotacoes nativas.
Conclusao
Tipagem estatica com mypy eleva a qualidade do codigo Python a outro patamar. Ela reduz bugs, melhora a legibilidade e facilita refatoracoes em projetos de qualquer tamanho. Comece com type hints basicos, configure o mypy no seu projeto e evolua gradualmente para generics e protocolos. O investimento inicial se paga rapidamente em menos erros em producao e maior confianca nas mudancas de codigo.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português