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.

5 min de leitura Equipe Python Brasil

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.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português