O match case é uma das adições mais revolucionárias ao Python desde a versão 3.10. Inspirado em linguagens como Rust, Scala e Haskell, o pattern matching estrutural permite que você escreva código mais limpo, expressivo e seguro quando precisa tomar decisões baseadas na estrutura de dados.
Diferente do tradicional if-elif-else, o match case não apenas compara valores — ele desestrutura dados, verifica tipos, combina padrões aninhados e captura variáveis automaticamente. Neste guia completo, você vai aprender tudo sobre essa poderosa ferramenta.
O Que é o Match Case?
O match é uma nova declaração condicional introduzida pelo Python 3.10 através da PEP 634. Ela permite comparar um valor contra uma série de padrões de forma estruturada. Cada padrão pode incluir literais, variáveis, tipos, sequências, mapeamentos e até mesmo padrões aninhados complexos.
A sintaxe básica é simples e intuitiva:
match valor:
case padrao_1:
# ação para padrao_1
case padrao_2:
# ação para padrao_2
case _:
# padrão curinga (default)
O caractere _ no último caso é o padrão curinga (wildcard), que corresponde a qualquer valor não capturado pelos casos anteriores. A documentação oficial do Python detalha toda a especificação na PEP 634.
Por Que Usar Match Case?
Antes do match case, lidar com múltiplas condições e formatos de dados geralmente resultava em cadeias enormes de if-elif-else ou dicionários de dispatch. O match case resolve isso de forma elegante:
- Legibilidade: O código fica mais declarativo e fácil de entender
- Segurança: O compilador verifica se todos os casos foram cobertos
- Expressividade: Padrões aninhados, guards e desestruturação em uma única declaração
- Performance: Implementação otimizada em C no CPython
Vamos explorar cada um desses benefícios com exemplos práticos.
Sintaxe Básica e Primeiros Exemplos
Vamos começar com um exemplo simples que simula um comando de terminal:
def processar_comando(comando):
match comando:
case "sair":
return "Encerrando programa..."
case "ajuda":
return "Comandos disponíveis: sair, ajuda, versao"
case "versao":
return "Python Pattern Matching v1.0"
case _:
return f"Comando desconhecido: {comando}"
print(processar_comando("ajuda"))
print(processar_comando("sair"))
print(processar_comando("invalido"))
Este exemplo substitui perfeitamente uma cadeia de if-elif-else. Mas o match case vai muito além da simples comparação de literais.
Correspondência com Literais
O match case pode comparar contra qualquer literal Python, incluindo inteiros, strings, booleanos e None:
def classificar_resposta(resposta):
match resposta:
case True:
return "Resposta positiva"
case False:
return "Resposta negativa"
case None:
return "Sem resposta"
case 42:
return "Resposta universal!"
case "talvez":
return "Resposta indecisa"
case _:
return "Resposta não reconhecida"
print(classificar_resposta(42))
print(classificar_resposta(None))
Segundo a PEP 635 (Motivação e Racional), a correspondência com literais é a base do pattern matching e oferece uma sintaxe muito mais limpa que switch-case tradicional de outras linguagens.
Correspondência com Variáveis
Um dos recursos mais úteis do match case é a capacidade de capturar valores em variáveis durante a correspondência:
def saudacao_personalizada(nome):
match nome:
case "Admin":
return "Bem-vindo, Administrador!"
case outro_nome:
return f"Olá, {outro_nome}!"
print(saudacao_personalizada("Admin"))
print(saudacao_personalizada("Maria"))
No exemplo acima, outro_nome captura qualquer valor que não seja "Admin". Isso é extremamente poderoso quando combinado com outros padrões.
Correspondência com Sequências
O match case brilha quando usado para desestruturar sequências como listas e tuplas. Você pode verificar o tamanho e capturar elementos específicos simultaneamente:
def analisar_coordenadas(ponto):
match ponto:
case [x, y]:
return f"Ponto 2D em ({x}, {y})"
case [x, y, z]:
return f"Ponto 3D em ({x}, {y}, {z})"
case [x, y, z, w]:
return f"Ponto 4D em ({x}, {y}, {z}, {w})"
case _:
return "Formato de coordenada inválido"
print(analisar_coordenadas([10, 20]))
print(analisar_coordenadas([1, 2, 3]))
print(analisar_coordenadas([]))
Você também pode usar * para capturar o resto da sequência, similar ao *args:
def primeiro_e_resto(lista):
match lista:
case [primeiro, *resto]:
return f"Primeiro: {primeiro}, Resto: {resto}"
case []:
return "Lista vazia"
print(primeiro_e_resto([1, 2, 3, 4, 5]))
print(primeiro_e_resto([]))
O tutorial oficial da PEP 636 demonstra diversos exemplos práticos de correspondência com sequências que vão desde análise de dados até processamento de linguagem natural.
Correspondência com Dicionários
Uma das aplicações mais úteis do match case é a correspondência com dicionários, especialmente em APIs que retornam JSON:
def processar_api_resposta(resposta):
match resposta:
case {"status": "ok", "dados": dados}:
return f"Dados recebidos: {dados}"
case {"status": "erro", "mensagem": msg}:
return f"Erro na API: {msg}"
case {"status": "erro"}:
return "Erro desconhecido na API"
case _:
return "Resposta inválida"
api_ok = {"status": "ok", "dados": {"usuario": "joao", "id": 1}}
api_erro = {"status": "erro", "mensagem": "404 Not Found"}
print(processar_api_resposta(api_ok))
print(processar_api_resposta(api_erro))
O match case verifica automaticamente se as chaves existem e captura os valores correspondentes. Aprender a trabalhar com dicionários é fundamental para dominar esse padrão — nosso guia de dicionários em Python pode ajudar com os fundamentos.
Correspondência com Classes e Objetos
O match case pode desestruturar objetos diretamente, verificando a classe e capturando atributos:
from dataclasses import dataclass
@dataclass
class Usuario:
nome: str
email: str
plano: str
@dataclass
class Admin:
nome: str
email: str
nivel: int
def descreverusuario(usuario):
match usuario:
case Admin(nome=nome, nivel=nivel):
return f"Admin {nome} (nível {nivel})"
case Usuario(nome=nome, plano="premium"):
return f"Usuário premium: {nome}"
case Usuario(nome=nome):
return f"Usuário comum: {nome}"
case :
return "Tipo de usuário desconhecido"
user1 = Usuario("Ana", "[email protected]", "premium")
user2 = Admin("Carlos", "[email protected]", 3)
user3 = Usuario("João", "[email protected]", "basico")
print(descrever_usuario(user1))
print(descrever_usuario(user2))
print(descrever_usuario(user3))
Essa funcionalidade permite escrever código orientado a objetos muito mais expressivo. O tutorial da Real Python explora em profundidade a correspondência com classes e padrões nomeados.
Padrões Aninhados
O verdadeiro poder do match case aparece quando você combina múltiplos padrões de forma aninhada:
def analisar_pedido(pedido):
match pedido:
case {"tipo": "comida", "item": item, "quantidade": qtd}:
return f"Pedido de comida: {qtd}x {item}"
case {"tipo": "bebida", "item": item, "quantidade": qtd, "tamanho": tam}:
return f"Bebida: {tam} {item} ({qtd}x)"
case {"tipo": "eletronico", "item": item}:
return f"Eletrônico: {item}"
case {"tipo": tipo, "item": item}:
return f"Item genérico do tipo {tipo}: {item}"
case _:
return "Pedido inválido"
pedido1 = {"tipo": "comida", "item": "Pizza", "quantidade": 2}
pedido2 = {"tipo": "bebida", "item": "Suco", "quantidade": 1, "tamanho": "Grande"}
pedido3 = {"tipo": "eletronico", "item": "Fone Bluetooth"}
print(analisar_pedido(pedido1))
print(analisar_pedido(pedido2))
print(analisar_pedido(pedido3))
O artigo do GeeksforGeeks sobre match case oferece mais exemplos de padrões aninhados aplicados a problemas do mundo real.
Guardas (Case ... If)
Às vezes você precisa adicionar condições extras além da correspondência de padrão. Para isso existem as guardas (guards):
def classificar_numero(n):
match n:
case _ if n < 0:
return f"{n} é negativo"
case _ if n == 0:
return "Zero"
case _ if n < 10:
return f"{n} é um dígito"
case _ if n < 100:
return f"{n} está entre 10 e 99"
case _:
return f"{n} é maior ou igual a 100"
print(classificar_numero(-5))
print(classificar_numero(0))
print(classificar_numero(7))
print(classificar_numero(42))
print(classificar_numero(1000))
Guards são avaliados após a correspondência do padrão e permitem criar lógicas condicionais complexas de forma muito mais limpa que múltiplos ifs aninhados.
Correspondência com OR (|)
Você pode usar o operador pipe (|) para combinar múltiplos padrões em um único caso:
def dia_da_semana(dia):
match dia:
case "segunda" | "terca" | "quarta" | "quinta" | "sexta":
return "Dia útil"
case "sabado" | "domingo":
return "Final de semana"
case _:
return "Dia inválido"
print(dia_da_semana("segunda"))
print(dia_da_semana("sabado"))
print(dia_da_semana("invalido"))
Essa sintaxe torna o código muito mais compacto que uma série de condições OR dispersas.
Correspondência com Enumerações
O match case combina perfeitamente com Enum para modelar estados finitos:
from enum import Enum, auto
class EstadoPedido(Enum):
PENDENTE = auto()
PAGO = auto()
ENVIADO = auto()
ENTREGUE = auto()
CANCELADO = auto()
def statuspedido(estado):
match estado:
case EstadoPedido.PENDENTE:
return "Pedido aguardando pagamento"
case EstadoPedido.PAGO:
return "Pedido pago, aguardando envio"
case EstadoPedido.ENVIADO:
return "Pedido enviado para entrega"
case EstadoPedido.ENTREGUE:
return "Pedido entregue com sucesso"
case EstadoPedido.CANCELADO:
return "Pedido cancelado"
case :
return "Estado desconhecido"
print(status_pedido(EstadoPedido.PENDENTE))
print(status_pedido(EstadoPedido.ENTREGUE))
Aplicações Práticas do Match Case
Vamos explorar alguns cenários do mundo real onde o match case se destaca:
1. Processamento de Comandos CLI
import sys
def interpretarcomando():
args = sys.argv[1:]
match args:
case ["buscar", *termos]:
return f"Buscando por: {' '.join(termos)}"
case ["baixar", url]:
return f"Baixando: {url}"
case ["config", chave, valor]:
return f"Configurando {chave} = {valor}"
case []:
return "Nenhum comando fornecido. Use --help para ajuda."
case :
return "Comando não reconhecido. Use --help para ajuda."
2. Tratamento de Erros com Tipos Específicos
def tratar_erro(erro):
match erro:
case ValueError(msg):
return f"Erro de valor: {msg}"
case TypeError(msg):
return f"Erro de tipo: {msg}"
case ConnectionError(msg):
return f"Erro de conexão: {msg}"
case Exception(msg):
return f"Erro genérico: {msg}"
case _:
return "Erro desconhecido"
try:
resultado = 10 / 0
except Exception as e:
print(tratar_erro(e))
O tutorial do LearnPython.com mostra como o match case pode simplificar drasticamente o tratamento de erros e a validação de entrada em aplicações reais.
3. Validação de Dados de Entrada
def validar_entrada(dados):
match dados:
case {"nome": str() as nome, "idade": int() as idade} if idade >= 18:
return f"Usuário {nome} validado (maior de idade)"
case {"nome": str() as nome, "idade": int() as idade}:
return f"Usuário {nome} menor de idade"
case {"nome": str()}:
return "Idade não fornecida"
case {}:
return "Dados incompletos"
case _:
return "Formato de dados inválido"
dados_validos = {"nome": "Ana", "idade": 25}
dados_menor = {"nome": "João", "idade": 15}
dados_sem_idade = {"nome": "Carlos"}
print(validar_entrada(dados_validos))
print(validar_entrada(dados_menor))
print(validar_entrada(dados_sem_idade))
Match Case vs If-Elif-Else
Quando usar match case em vez de if-elif-else?
| Situação | Match Case | If-Elif-Else |
|---|---|---|
| Comparar contra múltiplos literais | ✅ Excelente | ✅ Bom |
| Desestruturar sequências | ✅ Excelente | ❌ Ruim |
| Verificar tipos e atributos | ✅ Excelente | ⚠️ Médio |
| Condições complexas arbitrárias | ⚠️ Médio | ✅ Excelente |
| Padrões aninhados | ✅ Excelente | ❌ Ruim |
| Simples comparação booleana | ❌ Exagerado | ✅ Excelente |
A documentação oficial de controle de fluxo do Python fornece diretrizes adicionais sobre quando cada abordagem é mais adequada.
Limitações e Cuidados
Apesar de poderoso, o match case tem algumas limitações:
- Python 3.10+: Código antigo não suporta — lembre-se de verificar a compatibilidade
- Ordem importa: O primeiro caso correspondente é executado, então organize do mais específico para o mais genérico
- Não substitui if-else: Para condições booleanas simples, if-else ainda é mais apropriado
- Não tem break: Diferente do switch-case de C, não existe fall-through — apenas um caso é executado
Melhores Práticas
Para tirar o máximo proveito do match case, siga estas recomendações:
1. Sempre inclua um caso curinga
Use case _: no final para capturar valores inesperados:
match valor:
case 1:
print("Um")
case 2:
print("Dois")
case _:
print("Outro valor")
2. Ordene do específico para o genérico
match dados:
case [x, y, z]: # Mais específico primeiro
pass
case [x, y]: # Menos específico depois
pass
case _: # Genérico por último
pass
3. Use classes de dados para patterns mais limpos
Combinar @dataclass e match case produz o código mais expressivo. Entender bem os fundamentos de funções é essencial — nosso guia de funções em Python cobre tudo que você precisa saber.
Match Case no Ecossistema Python
O match case já está sendo adotado por diversas bibliotecas populares. O FastAPI usa pattern matching para roteamento avançado. Frameworks de processamento de dados como Pydantic v2 incorporaram padrões inspirados no match case para validação. O PEP 634 descreve a especificação completa que serviu de base para essas implementações.
Conclusão
O match case é uma ferramenta transformadora no Python moderno. Ele torna o código mais legível, seguro e expressivo, especialmente quando você precisa lidar com estruturas de dados complexas. Embora não substitua completamente o if-elif-else, o pattern matching oferece uma alternativa muito superior para uma ampla gama de cenários.
Pratique os exemplos deste guia, experimente criar seus próprios padrões e você descobrirá que o match case rapidamente se tornará um dos seus recursos favoritos do Python.
Continue explorando o Universo Python para mais conteúdos sobre Python moderno e boas práticas de desenvolvimento!