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.

8 min de leitura Equipe Python Brasil

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/else com 2-3 condições, não complique com match/case
  • Comparações numéricas puras: if x > 10 é mais claro que um guard em match
  • 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:

  1. Coloque os padrões mais frequentes primeiro — o Python testa os cases na ordem
  2. Use padrões específicos antes dos genéricos — evite que o wildcard capture tudo
  3. 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.

E

Equipe Python Brasil

Contribuidor do Python Brasil — Aprenda Python em Português