Python

Dominando Mypy em Python: Verificação Estática de Tipos no Projeto Real em Projetos Reais

12 min de leitura

Dominando Mypy em Python: Verificação Estática de Tipos no Projeto Real em Projetos Reais

Introdução ao Mypy: Por Que Type Checking Importa Quando você trabalha em projetos Python de médio a grande porte, a falta de verificação de tipos pode se tornar um pesadelo. Python é dinamicamente tipado, o que significa que os tipos são verificados apenas em tempo de execução. Isso é ótimo para prototipagem rápida, mas desastroso para produção quando um erro de tipo chega ao usuário final. Mypy é uma ferramenta de verificação estática de tipos que analisa seu código antes da execução, identificando incompatibilidades de tipo sem rodar uma única linha. Ele usa as type hints — anotações de tipo que você adiciona ao código — para garantir que você está usando funções, variáveis e objetos corretamente. Não é um validador de lógica, mas é extremamente eficaz em pegar bugs silenciosos causados por tipos incorretos. Fundamentos: Type Hints e Anotações de Tipo O que são Type Hints? Type hints são simplesmente anotações que informam qual tipo de dado uma variável,

<h2>Introdução ao Mypy: Por Que Type Checking Importa</h2>

<p>Quando você trabalha em projetos Python de médio a grande porte, a falta de verificação de tipos pode se tornar um pesadelo. Python é dinamicamente tipado, o que significa que os tipos são verificados apenas em tempo de execução. Isso é ótimo para prototipagem rápida, mas desastroso para produção quando um erro de tipo chega ao usuário final.</p>

<p>Mypy é uma ferramenta de verificação estática de tipos que analisa seu código antes da execução, identificando incompatibilidades de tipo sem rodar uma única linha. Ele usa as <strong>type hints</strong> — anotações de tipo que você adiciona ao código — para garantir que você está usando funções, variáveis e objetos corretamente. Não é um validador de lógica, mas é extremamente eficaz em pegar bugs silenciosos causados por tipos incorretos.</p>

<h2>Fundamentos: Type Hints e Anotações de Tipo</h2>

<h3>O que são Type Hints?</h3>

<p>Type hints são simplesmente anotações que informam qual tipo de dado uma variável, parâmetro ou retorno de função deve ter. Elas são completamente opcionais em Python — o código roda normalmente sem elas — mas são essenciais para o Mypy funcionar.</p>

<p>Vamos começar simples:</p>

<pre><code class="language-python"># Sem type hints (código válido, mas sem informação de tipo)

def saudacao(nome):

return f&quot;Olá, {nome}!&quot;

Com type hints (código idêntico, mas com informação)

def saudacao(nome: str) -&gt; str:

return f&quot;Olá, {nome}!&quot;</code></pre>

<p>No segundo caso, estamos dizendo: &quot;o parâmetro <code>nome</code> deve ser uma string, e a função retorna uma string&quot;. Se alguém chamar <code>saudacao(123)</code>, o Mypy vai reclamar, mesmo que Python permita.</p>

<h3>Tipos Básicos e Estruturas Complexas</h3>

<p>Os tipos mais comuns vêm direto de Python: <code>int</code>, <code>str</code>, <code>bool</code>, <code>float</code>. Mas frequentemente você trabalha com coleções e estruturas mais complexas. Para isso, usamos o módulo <code>typing</code>:</p>

<pre><code class="language-python">from typing import List, Dict, Optional, Union, Tuple

Lista de inteiros

numeros: List[int] = [1, 2, 3, 4]

Dicionário com chaves string e valores inteiros

idades: Dict[str, int] = {&quot;Alice&quot;: 30, &quot;Bob&quot;: 25}

Tipo que pode ser None (None é um tipo válido)

resultado: Optional[str] = None

Um tipo ou outro

status: Union[int, str] = &quot;ativo&quot;

Tupla com tipos específicos

coordenadas: Tuple[float, float] = (10.5, 20.3)</code></pre>

<p>Quando você usa <code>Optional[str]</code>, está dizendo que o valor pode ser uma string <strong>ou</strong> <code>None</code>. Sem essa anotação, código como <code>valor = funcao_que_retorna_none()</code> e depois <code>print(valor.upper())</code> passaria despercebido e quebraria em produção.</p>

<h2>Mypy na Prática: Configuração e Execução</h2>

<h3>Instalação e Setup Básico</h3>

<p>Mypy é uma ferramenta independente do Python. Instale via pip:</p>

<pre><code class="language-bash">pip install mypy</code></pre>

<p>Depois, execute sobre seu código:</p>

<pre><code class="language-bash">mypy seu_arquivo.py</code></pre>

<p>Para verificar um projeto inteiro:</p>

<pre><code class="language-bash">mypy .</code></pre>

<p>Mypy criará um arquivo <code>.mypy_cache/</code> para otimizar futuras análises. É seguro adicionar isso ao <code>.gitignore</code>.</p>

<h3>Configuração com pyproject.toml</h3>

<p>Para projetos profissionais, você quer configurar regras padrão. Crie um arquivo <code>pyproject.toml</code> na raiz:</p>

<pre><code class="language-toml">[tool.mypy]

python_version = &quot;3.10&quot;

warn_return_any = true

warn_unused_configs = true

disallow_untyped_defs = true

disallow_incomplete_defs = true

check_untyped_defs = true

no_implicit_optional = true

warn_redundant_casts = true

warn_unused_ignores = true

warn_no_return = true

strict = true</code></pre>

<p>Essas configurações ativam o modo &quot;strict&quot;, que é a forma mais rigorosa. <code>disallow_untyped_defs = true</code> força você a anotar <strong>todas</strong> as funções. Isso parece restritivo, mas é exatamente o que projetos sérios precisam.</p>

<h3>Um Exemplo Real: Sistema de Cadastro</h3>

<p>Vamos criar um exemplo realista com problemas que Mypy detecta:</p>

<pre><code class="language-python">from typing import List, Optional

from dataclasses import dataclass

@dataclass

class Usuario:

id: int

nome: str

email: str

idade: Optional[int] = None

class Banco:

def __init__(self) -&gt; None:

self.usuarios: List[Usuario] = []

def adicionar(self, usuario: Usuario) -&gt; None:

&quot;&quot;&quot;Adiciona um usuário ao banco.&quot;&quot;&quot;

self.usuarios.append(usuario)

def buscar_por_id(self, id: int) -&gt; Optional[Usuario]:

&quot;&quot;&quot;Retorna o usuário com o ID especificado ou None.&quot;&quot;&quot;

for usuario in self.usuarios:

if usuario.id == id:

return usuario

return None

def listar_emails(self) -&gt; List[str]:

&quot;&quot;&quot;Retorna lista de todos os emails.&quot;&quot;&quot;

return [u.email for u in self.usuarios]

Uso correto

banco = Banco()

novo_usuario = Usuario(id=1, nome=&quot;Alice&quot;, email=&quot;alice@example.com&quot;, idade=30)

banco.adicionar(novo_usuario)

usuario_encontrado = banco.buscar_por_id(1)

if usuario_encontrado:

print(usuario_encontrado.nome)</code></pre>

<p>Se você tentar fazer algo errado, Mypy avisa:</p>

<pre><code class="language-python"></code></pre>

<h2>Padrões Avançados: Generics, Protocolos e Type Aliases</h2>

<h3>Generics: Escrevendo Código Reutilizável com Tipos</h3>

<p>Muitas vezes você quer escrever funções ou classes que funcionam com qualquer tipo, mas ainda quer segurança de tipos. Para isso, usamos <strong>type variables</strong>:</p>

<pre><code class="language-python">from typing import TypeVar, List, Generic

T = TypeVar(&#039;T&#039;) # Um tipo genérico que pode ser qualquer coisa

def primeira_elemento(lista: List[T]) -&gt; Optional[T]:

&quot;&quot;&quot;Retorna o primeiro elemento da lista, ou None se vazia.&quot;&quot;&quot;

if lista:

return lista[0]

return None

Mypy entende que se você passa List[int], retorna Optional[int]

resultado_int = primeira_elemento([1, 2, 3]) # tipo: Optional[int]

resultado_str = primeira_elemento([&quot;a&quot;, &quot;b&quot;]) # tipo: Optional[str]</code></pre>

<p>Você também pode criar classes genéricas:</p>

<pre><code class="language-python">from typing import Generic, TypeVar

T = TypeVar(&#039;T&#039;)

class Caixa(Generic[T]):

&quot;&quot;&quot;Uma caixa que armazena um item de qualquer tipo.&quot;&quot;&quot;

def __init__(self, conteudo: T) -&gt; None:

self.conteudo = conteudo

def obter(self) -&gt; T:

return self.conteudo

Mypy entende os tipos específicos

caixa_numero = Caixa(42)

valor = caixa_numero.obter() # tipo: int

caixa_texto = Caixa(&quot;Python&quot;)

texto = caixa_texto.obter() # tipo: str</code></pre>

<h3>Protocolos: Interfaces Estruturais</h3>

<p>Às vezes você quer dizer &quot;qualquer objeto que tenha esses métodos&quot; sem se importar com herança. Isso é um <strong>Protocol</strong>:</p>

<pre><code class="language-python">from typing import Protocol

class Persistivel(Protocol):

&quot;&quot;&quot;Qualquer coisa que possa ser salva.&quot;&quot;&quot;

def salvar(self) -&gt; None: ...

class Documento:

def salvar(self) -&gt; None:

print(&quot;Documento salvo em disco&quot;)

class Email:

def salvar(self) -&gt; None:

print(&quot;Email arquivado&quot;)

def arquivar(item: Persistivel) -&gt; None:

&quot;&quot;&quot;Aceita qualquer objeto com método salvar().&quot;&quot;&quot;

item.salvar()

Funciona com Documento e Email sem herança explícita

arquivar(Documento())

arquivar(Email())</code></pre>

<h3>Type Aliases: Nomes Legíveis para Tipos Complexos</h3>

<p>Quando você tem tipos complexos, crie aliases:</p>

<pre><code class="language-python">from typing import Dict, List, Tuple

Tipo complexo sem alias (ilegível)

def processar_dados(dados: Dict[str, List[Tuple[int, str]]]) -&gt; None:

pass

Com alias (muito melhor)

Registro = Tuple[int, str]

Tabela = Dict[str, List[Registro]]

def processar_dados(dados: Tabela) -&gt; None:

pass</code></pre>

<h2>Integração em Projetos Reais: CI/CD e Boas Práticas</h2>

<h3>Mypy no Pipeline de CI/CD</h3>

<p>Adicione Mypy ao seu pipeline de testes. Exemplo com GitHub Actions:</p>

<pre><code class="language-yaml">name: Type Check

on: [push, pull_request]

jobs:

mypy:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v3
  • uses: actions/setup-python@v4

with:

python-version: &quot;3.10&quot;

  • run: pip install mypy
  • run: mypy src/</code></pre>

<p>Agora todo PR que quebra os tipos será rejeitado automaticamente.</p>

<h3>Suppressões Controladas</h3>

<p>Às vezes você precisa ignorar um erro de tipo por uma razão legítima. Use <code># type: ignore</code>:</p>

<pre><code class="language-python"># Ignorar um erro específico na linha

dados = json.loads(entrada) # type: ignore[arg-type]

Ignorar toda a linha

funcao_antiga_sem_tipos() # type: ignore

Ignorar um bloco inteiro

mypy: ignore-errors

def codigo_legado():

pass</code></pre>

<p>Use com moderação! Cada <code># type: ignore</code> deve ter um comentário explicando o porquê.</p>

<h3>Estratégia de Adoção Gradual</h3>

<p>Se você herdou um codebase sem tipos, não precisa anotar tudo de uma vez. Use <code># mypy: allow-untyped-defs</code> para arquivos específicos:</p>

<pre><code class="language-python"># mypy: allow-untyped-defs

Este arquivo é legado. Anotaremos gradualmente.

def funcao_antiga(x):

return x * 2

def funcao_nova(valor: int) -&gt; int: # Novas funções com tipos

return valor * 2</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu que <strong>Mypy traz confiabilidade a projetos Python através de verificação estática de tipos</strong>, permitindo detectar bugs antes da execução sem adicionar overhead em runtime. Em segundo lugar, <strong>type hints não são apenas anotações cosméticas — eles documentam contratos de código e permitem que ferramentas como IDEs e Mypy façam seu trabalho</strong>. Por fim, <strong>a adoção de Mypy em projetos profissionais é progressiva</strong>: comece com o modo padrão, evolua para o modo strict, e integre ao CI/CD para garantir qualidade consistente.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://mypy.readthedocs.io/" target="_blank" rel="noopener noreferrer">Documentação Oficial do Mypy</a></li>

<li><a href="https://www.python.org/dev/peps/pep-0484/" target="_blank" rel="noopener noreferrer">PEP 484 - Type Hints</a></li>

<li><a href="https://realpython.com/python-type-checking/" target="_blank" rel="noopener noreferrer">Real Python - Type Checking in Python</a></li>

<li><a href="https://docs.python.org/3/library/typing.html" target="_blank" rel="noopener noreferrer">Documentação do módulo typing</a></li>

<li><a href="https://google.github.io/styleguide/pyguide.html#type-comments" target="_blank" rel="noopener noreferrer">Google Python Style Guide - Type Comments</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Python

Dominando Decorators em Python: Criando, Empilhando e Parametrizando em Projetos Reais
Dominando Decorators em Python: Criando, Empilhando e Parametrizando em Projetos Reais

Entendendo Decorators: O Conceito Fundamental Um decorator em Python é uma fu...

Closures e Funções de Primeira Classe em Python: Do Básico ao Avançado
Closures e Funções de Primeira Classe em Python: Do Básico ao Avançado

Funções como Objetos de Primeira Classe Em Python, tudo é um objeto — incluin...

Testes Parametrizados e Property-Based Testing com Hypothesis: Do Básico ao Avançado
Testes Parametrizados e Property-Based Testing com Hypothesis: Do Básico ao Avançado

O Problema Tradicional dos Testes Unitários Quando começamos a escrever teste...