Pattern matching (match/case)
Esta aula aborda o pattern matching introduzido no Python 3.10 com a declaração match/case. Você aprenderá a sintaxe básica, os tipos de padrões, o uso de guardas e casos de uso práticos para tornar seu código mais expressivo e conciso.
O pattern matching, introduzido no Python 3.10 através da declaração match/case, é uma poderosa ferramenta para comparar valores contra padrões estruturais. Diferente de um simples switch-case de outras linguagens, o match/case permite desestruturar dados, combinar tipos e condições, tornando o código mais legível e expressivo.
Nesta aula, exploraremos a sintaxe, os tipos de padrões, o uso de guardas (condições adicionais) e casos de uso reais. Ao final, você será capaz de aplicar pattern matching em seus projetos Python modernos.
Sintaxe (Python 3.10+)
A estrutura básica do match/case é semelhante a um switch, mas com muito mais flexibilidade. A palavra-chave match inicia um bloco, seguida por um valor a ser comparado. Cada case define um padrão a ser verificado. Se o padrão corresponder, o bloco de código associado é executado. O primeiro padrão correspondente é utilizado.
def saudacao(comando):
match comando:
case "ola":
print("Olá!")
case "tchau":
print("Tchau!")
case _:
print("Comando desconhecido")O padrão curinga _ (underline) corresponde a qualquer valor, funcionando como um default. É importante notar que o match/case não exige break; apenas o primeiro caso correspondente é executado.
Padrões
Os padrões podem ser literais, variáveis, sequências, mapeamentos, classes, ou combinações destes. Abaixo alguns exemplos:
- Padrão literal:
case 42:oucase "texto": - Padrão de variável:
case x:(atribui o valor a x) - Padrão de sequência:
case [a, b, c]:(desestrutura listas/tuplas) - Padrão de mapeamento:
case {"chave": valor}:(desestrutura dicionários) - Padrão de classe:
case Ponto(x, y):(com classes data class ou NamedTuple)
Exemplo com sequência:
def processa_lista(lista):
match lista:
case []:
print("Lista vazia")
case [x]:
print(f"Um elemento: {x}")
case [x, y]:
print(f"Dois elementos: {x} e {y}")
case _:
print("Mais de dois elementos")Exemplo com classe (usando dataclass):
from dataclasses import dataclass
@dataclass
class Ponto:
x: int
y: int
def localiza(ponto):
match ponto:
case Ponto(0, 0):
print("Origem")
case Ponto(0, y):
print(f"Eixo Y em {y}")
case Ponto(x, 0):
print(f"Eixo X em {x}")
case Ponto(x, y):
print(f"Ponto ({x}, {y})")Guardas
Guardas permitem adicionar uma condição extra a um padrão, usando a palavra-chave if após o padrão. A condição é avaliada apenas se o padrão corresponder. Se a condição for falsa, o próximo caso é testado.
def classifica_numero(n):
match n:
case 0:
print("Zero")
case n if n > 0:
print("Positivo")
case n if n < 0:
print("Negativo")Note que a variável n no caso com guarda é uma nova variável que captura o valor. Cuidado: se você usar o mesmo nome de uma variável externa, ela será sobrescrita no escopo do case. Use nomes diferentes ou o padrão curinga quando necessário.
Casos de uso
Pattern matching é útil em várias situações:
- Interpretação de comandos: processar comandos de uma CLI, como um parser simples.
- Manipulação de AST: em compiladores ou interpretadores, para tratar diferentes nós da árvore sintática.
- Processamento de dados: lidar com diferentes formatos de entrada (JSON, tuplas, etc.).
- Máquinas de estado: transições baseadas em eventos.
Exemplo prático: processar uma resposta de API que pode ser sucesso ou erro.
def trata_resposta(resposta):
match resposta:
case {"status": 200, "dados": dados}:
print(f"Sucesso: {dados}")
case {"status": 404}:
print("Não encontrado")
case {"status": status, "erro": msg}:
print(f"Erro {status}: {msg}")
case _:
print("Resposta desconhecida")Outro exemplo: combinar padrões com classes para simular uma álgebra de tipos.
Boas práticas e observações finais
- Use padrões curinga (
_) para capturar casos não previstos, garantindo exaustividade. - Evite repetir lógica; use guardas para condições complexas.
- Lembre-se que o match/case é uma expressão? Não, é uma declaração. Mas você pode usar match dentro de funções.
- Em Python 3.10+, o match/case está disponível; versões anteriores não suportam.
Com o pattern matching, seu código fica mais declarativo e próximo da intenção, reduzindo a necessidade de múltiplos if-elif.
Referências
- Documentação oficial do Python: match
- PEP 634 – Structural Pattern Matching: Specification
- PEP 635 – Structural Pattern Matching: Motivation and Rationale
- PEP 636 – Structural Pattern Matching: Tutorial
- Real Python: Python match/case Statement
- GeeksforGeeks: match case in Python
Exercícios
Escreva uma função que use match/case para classificar um número como 'positivo', 'negativo' ou 'zero' (sem usar guardas).
✓ Resposta:def classifica_numero(n): match n: case 0: return "zero" case n if n > 0: return "positivo" case n if n < 0: return "negativo"Crie uma função que receba uma lista e use match/case para retornar 'vazia', 'um elemento', 'dois elementos' ou 'muitos elementos'.
✓ Resposta:def tamanho_lista(lista): match lista: case []: return "vazia" case [x]: return "um elemento" case [x, y]: return "dois elementos" case _: return "muitos elementos"Escreva uma função que use match/case para processar um dicionário representando uma forma geométrica: {"tipo": "circulo", "raio": r} ou {"tipo": "retangulo", "largura": w, "altura": h}. Retorne a área.
✓ Resposta:import math def area(forma): match forma: case {"tipo": "circulo", "raio": r}: return math.pi * r * r case {"tipo": "retangulo", "largura": w, "altura": h}: return w * h case _: return NoneCrie uma função que receba uma tupla (x, y) e use match/case para determinar em qual quadrante o ponto se encontra (assumindo x e y diferentes de zero). Use guardas.
✓ Resposta:def quadrante(ponto): match ponto: case (x, y) if x > 0 and y > 0: return "primeiro" case (x, y) if x < 0 and y > 0: return "segundo" case (x, y) if x < 0 and y < 0: return "terceiro" case (x, y) if x > 0 and y < 0: return "quarto" case _: return "sobre eixo"Implemente uma função que receba um valor que pode ser int, str ou None e use match/case para retornar uma descrição: 'int: {valor}', 'str: {valor}', ou 'None'.
✓ Resposta:def descreve(valor): match valor: case int(n): return f"int: {n}" case str(s): return f"str: {s}" case None: return "None" case _: return "tipo desconhecido"