Pattern Matching em Python: Guia Completo do match/case | Python Brasil
Aprenda Pattern Matching em Python com match/case. Exemplos práticos, guards, destructuring e quando usar essa feature poderosa do Python moderno.
O Structural Pattern Matching (correspondência de padrões estruturais) chegou ao Python na versão 3.10 e é uma das adições mais poderosas à linguagem nos últimos anos. Com a sintaxe match/case, você pode escrever código mais expressivo, legível e robusto para lidar com dados complexos.
Neste guia completo, vamos explorar tudo sobre pattern matching em Python: desde o básico até padrões avançados com guards, destructuring e classes.
O que é Pattern Matching?
Pattern matching é uma técnica que permite comparar um valor contra uma série de padrões e executar código baseado no padrão que corresponder. Se você já programou em Rust, Scala ou Haskell, já conhece o conceito. Em Python, a sintaxe usa as palavras-chave match e case:
def analisar_comando(comando: str) -> str:
match comando:
case "iniciar":
return "Iniciando o sistema..."
case "parar":
return "Parando o sistema..."
case "reiniciar":
return "Reiniciando o sistema..."
case _:
return f"Comando desconhecido: {comando}"
print(analisar_comando("iniciar")) # Iniciando o sistema...
print(analisar_comando("pausar")) # Comando desconhecido: pausar
O _ (underscore) funciona como um wildcard — ele captura qualquer valor que não corresponda aos padrões anteriores, similar ao default de um switch em outras linguagens.
match/case vs if/elif: quando usar cada um?
À primeira vista, o match/case pode parecer apenas um switch/case glorificado. Mas ele vai muito além disso. Veja a diferença:
Com if/elif (antes)
def processar_resposta(status_code: int, corpo: dict) -> str:
if status_code == 200 and "dados" in corpo:
return f"Sucesso: {len(corpo['dados'])} itens"
elif status_code == 200 and "mensagem" in corpo:
return f"OK: {corpo['mensagem']}"
elif status_code == 404:
return "Não encontrado"
elif status_code >= 500:
return "Erro no servidor"
else:
return f"Status inesperado: {status_code}"
Com match/case (depois)
def processar_resposta(status_code: int, corpo: dict) -> str:
match (status_code, corpo):
case (200, {"dados": list() as itens}):
return f"Sucesso: {len(itens)} itens"
case (200, {"mensagem": str() as msg}):
return f"OK: {msg}"
case (404, _):
return "Não encontrado"
case (code, _) if code >= 500:
return "Erro no servidor"
case _:
return f"Status inesperado: {status_code}"
A versão com match/case é mais declarativa — você descreve a forma dos dados que espera, não apenas condições booleanas.
Tipos de padrões
O Python suporta vários tipos de padrões no match/case. Vamos explorar cada um.
1. Padrões literais
Comparam valores exatos:
def traduzir_dia(dia: str) -> str:
match dia.lower():
case "monday":
return "Segunda-feira"
case "tuesday":
return "Terça-feira"
case "wednesday":
return "Quarta-feira"
case "thursday":
return "Quinta-feira"
case "friday":
return "Sexta-feira"
case "saturday":
return "Sábado"
case "sunday":
return "Domingo"
case _:
return "Dia inválido"
2. Padrões de captura
Capturam o valor em uma variável:
def saudar(nome):
match nome:
case "admin":
return "Bem-vindo, administrador!"
case usuario:
return f"Olá, {usuario}!"
print(saudar("admin")) # Bem-vindo, administrador!
print(saudar("Maria")) # Olá, Maria!
3. Padrões de sequência (listas e tuplas)
Aqui o pattern matching começa a brilhar — decomponha listas e tuplas com facilidade:
def analisar_coordenadas(ponto):
match ponto:
case (0, 0):
return "Origem"
case (x, 0):
return f"Eixo X em {x}"
case (0, y):
return f"Eixo Y em {y}"
case (x, y):
return f"Ponto ({x}, {y})"
case (x, y, z):
return f"Ponto 3D ({x}, {y}, {z})"
case _:
return "Formato inválido"
print(analisar_coordenadas((0, 0))) # Origem
print(analisar_coordenadas((5, 0))) # Eixo X em 5
print(analisar_coordenadas((3, 4))) # Ponto (3, 4)
print(analisar_coordenadas((1, 2, 3))) # Ponto 3D (1, 2, 3)
Use *rest para capturar elementos restantes:
def primeiro_e_resto(itens):
match itens:
case []:
return "Lista vazia"
case [primeiro]:
return f"Um item: {primeiro}"
case [primeiro, *resto]:
return f"Primeiro: {primeiro}, restante: {resto}"
print(primeiro_e_resto([1, 2, 3, 4]))
# Primeiro: 1, restante: [2, 3, 4]
4. Padrões de mapeamento (dicionários)
Perfeito para trabalhar com JSON e dados de APIs:
def processar_evento(evento: dict):
match evento:
case {"tipo": "usuario_criado", "dados": {"nome": nome, "email": email}}:
return f"Novo usuário: {nome} ({email})"
case {"tipo": "pedido_feito", "dados": {"id": pedido_id, "total": total}}:
return f"Pedido #{pedido_id}: R$ {total:.2f}"
case {"tipo": "pagamento", "dados": {"status": "aprovado", "valor": valor}}:
return f"Pagamento aprovado: R$ {valor:.2f}"
case {"tipo": "pagamento", "dados": {"status": "recusado", "motivo": motivo}}:
return f"Pagamento recusado: {motivo}"
case {"tipo": tipo}:
return f"Evento não tratado: {tipo}"
case _:
return "Evento inválido"
# Exemplos
evento1 = {"tipo": "usuario_criado", "dados": {"nome": "Ana", "email": "ana@email.com"}}
evento2 = {"tipo": "pagamento", "dados": {"status": "recusado", "motivo": "saldo insuficiente"}}
print(processar_evento(evento1)) # Novo usuário: Ana (ana@email.com)
print(processar_evento(evento2)) # Pagamento recusado: saldo insuficiente
5. Padrões de classe
Combine pattern matching com dataclasses para código extremamente expressivo:
from dataclasses import dataclass
@dataclass
class Circulo:
raio: float
@dataclass
class Retangulo:
largura: float
altura: float
@dataclass
class Triangulo:
base: float
altura: float
def calcular_area(forma) -> float:
match forma:
case Circulo(raio=r):
from math import pi
return pi * r ** 2
case Retangulo(largura=l, altura=a):
return l * a
case Triangulo(base=b, altura=a):
return (b * a) / 2
case _:
raise ValueError(f"Forma desconhecida: {type(forma)}")
print(f"{calcular_area(Circulo(5)):.2f}") # 78.54
print(f"{calcular_area(Retangulo(4, 6)):.2f}") # 24.00
print(f"{calcular_area(Triangulo(3, 8)):.2f}") # 12.00
6. Padrões com guards (condições adicionais)
Use if após o case para adicionar condições:
def classificar_nota(nota: float) -> str:
match nota:
case n if n < 0 or n > 10:
return "Nota inválida"
case n if n >= 9:
return "Excelente"
case n if n >= 7:
return "Bom"
case n if n >= 5:
return "Regular"
case _:
return "Insuficiente"
print(classificar_nota(9.5)) # Excelente
print(classificar_nota(6.0)) # Regular
print(classificar_nota(3.0)) # Insuficiente
7. Padrões OR (alternativas)
Use | para combinar múltiplos padrões:
def classificar_http_status(code: int) -> str:
match code:
case 200 | 201 | 204:
return "Sucesso"
case 301 | 302 | 307:
return "Redirecionamento"
case 400 | 422:
return "Erro do cliente"
case 401 | 403:
return "Não autorizado"
case 404:
return "Não encontrado"
case 500 | 502 | 503:
return "Erro do servidor"
case _:
return f"Status desconhecido: {code}"
Exemplo prático: parser de comandos CLI
Vamos criar um exemplo mais completo — um parser de comandos para uma aplicação de gerenciamento de tarefas:
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class Tarefa:
titulo: str
concluida: bool = False
criada_em: datetime = field(default_factory=datetime.now)
class GerenciadorTarefas:
def __init__(self):
self.tarefas: list[Tarefa] = []
def executar(self, comando: str) -> str:
partes = comando.strip().split(maxsplit=1)
match partes:
case ["add" | "adicionar", titulo]:
self.tarefas.append(Tarefa(titulo=titulo))
return f"Tarefa adicionada: {titulo}"
case ["done" | "concluir", indice_str] if indice_str.isdigit():
indice = int(indice_str) - 1
if 0 <= indice < len(self.tarefas):
self.tarefas[indice].concluida = True
return f"Tarefa {indice + 1} concluída!"
return "Índice inválido"
case ["list" | "listar"]:
if not self.tarefas:
return "Nenhuma tarefa cadastrada"
linhas = []
for i, t in enumerate(self.tarefas, 1):
status = "✓" if t.concluida else "○"
linhas.append(f" {i}. [{status}] {t.titulo}")
return "\n".join(linhas)
case ["remove" | "remover", indice_str] if indice_str.isdigit():
indice = int(indice_str) - 1
if 0 <= indice < len(self.tarefas):
removida = self.tarefas.pop(indice)
return f"Removida: {removida.titulo}"
return "Índice inválido"
case ["help" | "ajuda"]:
return (
"Comandos disponíveis:\n"
" add <título> - Adicionar tarefa\n"
" done <número> - Concluir tarefa\n"
" list - Listar tarefas\n"
" remove <número> - Remover tarefa"
)
case [cmd, *_]:
return f"Comando desconhecido: {cmd}. Digite 'help' para ajuda."
case _:
return "Digite um comando. Use 'help' para ver opções."
# Uso
gerenciador = GerenciadorTarefas()
print(gerenciador.executar("add Estudar pattern matching"))
print(gerenciador.executar("add Ler documentação do Python"))
print(gerenciador.executar("done 1"))
print(gerenciador.executar("listar"))
Saída:
Tarefa adicionada: Estudar pattern matching
Tarefa adicionada: Ler documentação do Python
Tarefa 1 concluída!
1. [✓] Estudar pattern matching
2. [○] Ler documentação do Python
Quando NÃO usar match/case
Apesar de poderoso, o pattern matching não é a melhor escolha para tudo:
- Condições simples: para um
if/elsecom 2-3 condições, não complique commatch/case - Comparações numéricas puras:
if x > 10é mais claro que um guard emmatch - Compatibilidade com Python < 3.10: se seu projeto precisa rodar em versões mais antigas
Dicas de performance
O pattern matching em Python é implementado de forma eficiente pelo interpretador. Algumas dicas:
- Coloque os padrões mais frequentes primeiro — o Python testa os cases na ordem
- Use padrões específicos antes dos genéricos — evite que o wildcard capture tudo
- Prefira padrões de classe a verificações manuais com
isinstance()
Perguntas Frequentes
O match/case é a mesma coisa que switch/case?
Não exatamente. O match/case do Python vai muito além de um simples switch. Ele suporta destructuring, padrões aninhados, guards e correspondência por tipo — features que um switch tradicional não oferece.
Preciso de Python 3.10 ou superior?
Sim. O structural pattern matching foi introduzido no Python 3.10 (PEP 634). Se você usa uma versão anterior, confira qual versão do Python usar.
O match/case é mais rápido que if/elif?
Em termos de performance, a diferença é negligível para a maioria dos casos. A vantagem do match/case é legibilidade e expressividade, não velocidade.
Posso usar match/case com expressões regulares?
Não diretamente. Mas você pode combinar com guards:
import re
def validar_entrada(texto: str) -> str:
match texto:
case s if re.match(r'^\d{3}\.\d{3}\.\d{3}-\d{2}$', s):
return "CPF válido"
case s if re.match(r'^[\w.]+@[\w.]+\.\w+$', s):
return "Email válido"
case _:
return "Formato não reconhecido"
Posso ter match/case aninhados?
Sim, você pode aninhar match/case dentro de um case, embora isso possa prejudicar a legibilidade. Prefira extrair lógica complexa para funções separadas.
Conclusão
O structural pattern matching é uma das melhores adições ao Python moderno. Ele torna o código mais declarativo, expressivo e fácil de manter — especialmente ao lidar com dados estruturados como JSON de APIs, eventos, comandos e tipos heterogêneos.
Se você está trabalhando com Python 3.10+, comece a incorporar match/case no seu código onde fizer sentido. Combine com dataclasses, type hints e Pydantic para escrever Python moderno e robusto.
Quer ver mais conteúdos sobre Python moderno? Confira nosso post sobre boas práticas Python em 2026 e explore o glossário Python para dominar a terminologia.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português