Pattern matching (match/case)
Esta aula aborda o pattern matching introduzido no Python 3.10 com a declaração match/case, explorando sua sintaxe, tipos de padrões, uso de guardas e casos de uso práticos. O conteúdo é voltado para alunos intermediários que desejam escrever código mais expressivo e conciso.
O pattern matching (correspondência de padrões) é um recurso poderoso introduzido no Python 3.10 por meio da declaração match/case. Inspirado em linguagens como Haskell, Rust e Scala, ele permite comparar um valor contra uma série de padrões de forma clara e concisa, substituindo longas cadeias de if/elif/else ou consultas complexas a dicionários. Nesta aula, você aprenderá a sintaxe básica, os diferentes tipos de padrões, como usar guardas para condições adicionais e exemplos práticos de aplicação.
O pattern matching não é apenas um simples switch case: ele suporta desestruturação, captura de variáveis, padrões aninhados e muito mais. Com ele, você pode escrever código mais legível e menos propenso a erros, especialmente ao lidar com estruturas de dados complexas.
Sintaxe (Python 3.10+)
A declaração match avalia uma expressão e tenta correspondê-la a um dos padrões listados nos blocos case. A sintaxe básica é:
match valor:
case padrão1:
# ação para padrão1
case padrão2:
# ação para padrão2
case _:
# padrão curinga (opcional)O padrão _ (underscore) funciona como curinga, correspondendo a qualquer valor. É importante que os casos sejam exaustivos, embora o Python não exija isso obrigatoriamente; se nenhum padrão corresponder, a execução simplesmente continua após o bloco match.
Exemplo simples:
def saudacao(idioma):
match idioma:
case "pt":
print("Olá!")
case "en":
print("Hello!")
case "es":
print("¡Hola!")
case _:
print("Idioma desconhecido")
saudacao("pt") # Olá!Observe que não é necessário usar break como em outras linguagens; apenas o primeiro caso correspondente é executado.
Padrões
O Python suporta diversos tipos de padrões, que podem ser combinados para criar correspondências complexas.
Padrões literais: correspondem a valores específicos (números, strings, booleanos, None).
match x:
case 0:
print("zero")
case 1:
print("um")
case None:
print("nulo")Padrões de captura: atribuem o valor a uma variável, permitindo seu uso no bloco do caso.
match ponto:
case (0, 0):
print("Origem")
case (x, 0):
print(f"Eixo X em {x}")
case (0, y):
print(f"Eixo Y em {y}")
case (x, y):
print(f"Ponto ({x}, {y})")Padrões de sequência: correspondem a sequências (listas, tuplas) e podem desestruturar elementos.
match dados:
case [a, b]:
print(f"Lista de dois elementos: {a}, {b}")
case [a, b, c]:
print(f"Lista de três elementos: {a}, {b}, {c}")
case _:
print("Outro formato")Padrões de mapeamento: correspondem a dicionários, permitindo verificar chaves e capturar valores.
match usuario:
case {"nome": nome, "idade": idade}:
print(f"{nome} tem {idade} anos")
case {"nome": nome}:
print(f"{nome} idade desconhecida")
case _:
print("Dados inválidos")Padrões de classe: correspondem a instâncias de classes, desestruturando atributos.
class Ponto:
def __init__(self, x, y):
self.x = x
self.y = y
match p:
case Ponto(x=0, y=0):
print("Origem")
case Ponto(x=x, y=y):
print(f"Ponto ({x}, {y})")Padrões aninhados: qualquer combinação dos anteriores.
match dados:
case {"tipo": "retangulo", "dimensoes": [largura, altura]}:
print(f"Retângulo {largura}x{altura}")
case {"tipo": "circulo", "dimensoes": {"raio": r}}:
print(f"Círculo raio {r}")Guardas
Guardas são condições adicionais que devem ser verdadeiras para que um caso seja selecionado. Elas são escritas após a palavra-chave if no final do padrão.
match valor:
case x if x > 0:
print(f"Positivo: {x}")
case x if x < 0:
print(f"Negativo: {x}")
case 0:
print("Zero")Guardas são avaliadas somente após o padrão corresponder. Se a guarda for falsa, o padrão é descartado e a execução continua para o próximo caso.
Exemplo com desestruturação:
match ponto:
case (x, y) if x == y:
print(f"Diagonal: {x}")
case (x, y):
print(f"Ponto ({x}, {y})")É importante notar que as variáveis capturadas no padrão podem ser usadas na guarda.
Casos de uso
O pattern matching é útil em diversas situações, substituindo construções mais complexas.
1. Processamento de comandos: interpretar comandos de uma CLI.
def processar_comando(comando):
match comando.split():
case ["sair"]:
print("Encerrando...")
case ["ola", nome]:
print(f"Olá, {nome}!")
case ["soma", *numeros]:
print(f"Soma: {sum(map(int, numeros))}")
case _:
print("Comando inválido")2. Validação de dados: verificar estruturas recebidas de APIs.
def validar_resposta(resposta):
match resposta:
case {"status": 200, "dados": dados}:
return dados
case {"status": 404}:
raise ValueError("Não encontrado")
case {"status": status} if 500 <= status < 600:
raise ConnectionError("Erro no servidor")
case _:
raise ValueError("Resposta inesperada")3. Tratamento de eventos: em interfaces gráficas ou jogos.
def tratar_evento(evento):
match evento:
case ("clique", x, y):
print(f"Clique em ({x}, {y})")
case ("tecla", tecla) if tecla == "q":
print("Saindo...")
case ("tecla", tecla):
print(f"Tecla pressionada: {tecla}")
case _:
pass4. Expressões algébricas: implementar evaluadores.
def avaliar(expr):
match expr:
case ("soma", a, b):
return avaliar(a) + avaliar(b)
case ("mult", a, b):
return avaliar(a) * avaliar(b)
case int(n):
return n
case _:
raise ValueError("Expressão inválida")O pattern matching torna o código mais declarativo e fácil de entender, reduzindo a complexidade condicional.
Referências
- Documentação oficial do Python: match statement
- PEP 634 – Structural Pattern Matching: Specification
- PEP 635 – Structural Pattern Matching: Motivation and Rationale
- PEP 636 – Structural Pattern Matching: Tutorial
- Real Python: Python's Match Statement
- GeeksforGeeks: Match Case Statement in Python
Exercícios
Usando pattern matching, escreva uma função que recebe uma tupla (operacao, a, b) e retorna o resultado da operação. As operações podem ser 'soma', 'subtracao', 'multiplicacao', 'divisao'. Se a operação for desconhecida, retorne None.
✓ Resposta:def calculadora(expr): match expr: case ('soma', a, b): return a + b case ('subtracao', a, b): return a - b case ('multiplicacao', a, b): return a * b case ('divisao', a, b): return a / b if b != 0 else None case _: return NoneEscreva uma função que recebe uma lista e imprime seu conteúdo formatado: se a lista tiver um elemento, imprime "[elemento]"; se tiver dois, imprime "[a, b]"; se tiver três ou mais, imprime "[a, b, ...]". Use pattern matching.
✓ Resposta:def formatar_lista(lista): match lista: case [a]: print(f"[{a}]") case [a, b]: print(f"[{a}, {b}]") case [a, b, *_]: print(f"[{a}, {b}, ...]") case _: print("Lista vazia")Crie uma função que recebe um dicionário representando um pedido com chaves 'id', 'itens' (lista de strings) e 'total' (float). Use pattern matching para validar a estrutura: se faltar alguma chave, imprima "Pedido inválido"; caso contrário, imprima o id e o número de itens.
✓ Resposta:def processar_pedido(pedido): match pedido: case {"id": id, "itens": itens, "total": total}: print(f"Pedido {id} com {len(itens)} itens") case _: print("Pedido inválido")Escreva uma função que recebe um objeto (instância de classe) e, usando pattern matching, verifica se é uma instância de 'int', 'str', 'list' ou 'dict'. Se for int e maior que 100, imprima "Inteiro grande"; se for str e vazia, imprima "String vazia"; para list, imprima o primeiro elemento; para dict, imprima as chaves; caso contrário, imprima "Tipo desconhecido".
✓ Resposta:def analisar_objeto(obj): match obj: case int(n) if n > 100: print("Inteiro grande") case int(): print("Inteiro") case str(s) if s == "": print("String vazia") case str(): print("String") case list(primeiro, *_): print(f"Primeiro elemento: {primeiro}") case dict(**kwargs): print(f"Chaves: {list(kwargs.keys())}") case _: print("Tipo desconhecido")Implemente uma função que recebe uma expressão na forma de tupla aninhada representando uma árvore binária de números, onde 'soma' e 'mult' são operadores binários e números são folhas. Use pattern matching para avaliar a expressão.
✓ Resposta:def avaliar_expr(expr): match expr: case ('soma', a, b): return avaliar_expr(a) + avaliar_expr(b) case ('mult', a, b): return avaliar_expr(a) * avaliar_expr(b) case int(n): return n case _: raise ValueError("Expressão inválida")
Boas práticas e observações finais
Ao usar pattern matching, lembre-se de que a ordem dos casos importa: o primeiro padrão correspondente é executado. Coloque padrões mais específicos antes dos mais genéricos. Use guardas para condições adicionais, mas evite guardas muito complexas que possam prejudicar a legibilidade. O pattern matching é especialmente útil para desestruturar dados e tornar o código mais expressivo, mas não é adequado para substituir todos os if/else; use-o quando houver correspondência de padrões clara e benefício na legibilidade.