Instrução match do Python
Aprenda correspondência estrutural de padrões em Python com match/case: literais, sequências, mapeamentos, padrões de classe, guardas e curinga — com exemplos.
O Python 3.10 introduziu a correspondência estrutural de padrões por meio da instrução match — uma forma poderosa de ramificar com base na forma e no conteúdo dos dados, e não apenas na igualdade. Este capítulo aborda desde a sintaxe básica de match/case até padrões avançados como desempacotamento de sequências, padrões de mapeamento, padrões de classe, guardas e casos de uso do mundo real.
Antes de ler este capítulo, você deve estar familiarizado com if/else em Python, funções Python e estruturas de dados básicas (listas, tuplas, dicionários).
O que é Correspondência Estrutural de Padrões?
A correspondência estrutural de padrões permite inspecionar a estrutura de um objeto — seu tipo, os valores de seus campos, a forma de uma sequência — e executar código diferente dependendo de qual padrão se encaixa. Vai muito além de uma simples verificação if x == y.
Considere o roteamento de um código de status HTTP. Com cadeias de if/elif você escreve:
if status == 200:
print("OK")
elif status == 404:
print("Not Found")
elif status == 500:
print("Internal Server Error")
else:
print("Unknown status")Com match, a intenção fica mais clara:
match status:
case 200:
print("OK")
case 404:
print("Not Found")
case 500:
print("Internal Server Error")
case _:
print("Unknown status")A verdadeira vantagem aparece quando o sujeito é um objeto complexo — uma tupla, um dicionário ou um dataclass — e você quer desestruturá-lo enquanto faz a correspondência.
Sintaxe Básica
match subject:
case pattern1:
# runs if subject matches pattern1
case pattern2:
# runs if subject matches pattern2
case _:
# wildcard — runs if nothing else matchedRegras para lembrar:
matchecasesão palavras-chave suaves — elas são palavras-chave apenas neste contexto e ainda podem ser usadas como nomes de variáveis em outras partes do seu código.- Cada bloco
caseé testado em ordem; a primeira correspondência vence e as demais são ignoradas. - O bloco
case _:é o curinga — sempre corresponde e funciona como padrão de captura de tudo. - É necessário Python 3.10 ou superior. Executar isso no Python 3.9 ou anterior gera um
SyntaxError.
Padrões Literais
O padrão mais simples corresponde a um valor concreto: um número, uma string, True, False ou None.
def http_status(status):
match status:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return "Unknown status"
print(http_status(200)) # OK
print(http_status(404)) # Not Found
print(http_status(999)) # Unknown statusPadrões OR (|)
Use | dentro de um case para corresponder a qualquer um de vários literais:
def is_vowel(letter):
match letter.lower():
case "a" | "e" | "i" | "o" | "u":
return True
case _:
return False
print(is_vowel("a")) # True
print(is_vowel("b")) # False
print(is_vowel("E")) # TrueOs padrões OR também funcionam com números, None e outros tipos literais.
Padrões de Captura
Um padrão de captura é um nome simples (não uma string literal, não um nome com ponto) que corresponde a qualquer coisa e vincula o valor correspondido a esse nome para uso no corpo:
def greet(name):
match name:
case "Alice":
return "Hello, Alice!"
case other: # captures whatever was passed
return f"Hello, {other}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob")) # Hello, Bob!other acima é um padrão de captura — ele vincula o valor correspondido à variável local other. Isso se parece muito com o curinga _, mas _ descarta o valor enquanto uma captura nomeada o mantém.
Um nome simples em um case é sempre uma captura, nunca uma comparação. Se você quiser comparar com uma constante definida em outro lugar, use um nome com ponto como Status.OK ou envolva-o em uma guarda (case x if x == my_constant:).
Padrões de Sequência
Um padrão de sequência corresponde a listas, tuplas ou qualquer sequência, e pode desestruturar os elementos em variáveis ao mesmo tempo.
def process_point(point):
match point:
case (0, 0):
return "Origin"
case (x, 0):
return f"On x-axis at {x}"
case (0, y):
return f"On y-axis at {y}"
case (x, y):
return f"Point at ({x}, {y})"
print(process_point((0, 0))) # Origin
print(process_point((5, 0))) # On x-axis at 5
print(process_point((0, 3))) # On y-axis at 3
print(process_point((2, 4))) # Point at (2, 4)Usando * para Capturar o Restante
Um *name dentro de um padrão de sequência coleta os elementos restantes, assim como o desempacotamento de iteráveis:
def describe_list(items):
match items:
case []:
return "empty list"
case [single]:
return f"one item: {single}"
case [first, *rest]:
return f"starts with {first!r}, then {len(rest)} more item(s)"
print(describe_list([])) # empty list
print(describe_list([42])) # one item: 42
print(describe_list([1, 2, 3, 4])) # starts with 1, then 3 more item(s)Use [first, *_] se quiser capturar apenas o primeiro elemento e descartar o restante.
Padrões de Mapeamento
Um padrão de mapeamento corresponde a dicionários (ou qualquer Mapping). Você especifica apenas as chaves que lhe interessam — chaves extras no sujeito são ignoradas.
def process_event(event):
match event:
case {"type": "click", "button": button}:
return f"Mouse click: button {button}"
case {"type": "keypress", "key": key}:
return f"Key pressed: {key!r}"
case {"type": action}:
return f"Other event: {action}"
case _:
return "Unknown event"
print(process_event({"type": "click", "button": 1}))
# Mouse click: button 1
print(process_event({"type": "keypress", "key": "Enter"}))
# Key pressed: 'Enter'
print(process_event({"type": "resize", "width": 800}))
# Other event: resize
print(process_event({}))
# Unknown eventPonto-chave: um padrão de mapeamento nunca falha por causa de chaves extras no sujeito. {"type": "click", "button": button} corresponde mesmo que o evento também contenha coordenadas "x" e "y".
Para capturar os pares de chave/valor restantes, use **rest:
match event:
case {"type": "click", **rest}:
print(f"Click event with extra data: {rest}")Padrões de Classe
Um padrão de classe corresponde a uma instância de uma classe específica e extrai seus atributos. Isso é especialmente útil com dataclasses porque seus atributos são expostos por nome automaticamente.
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Circle:
center: Point
radius: float
def describe_shape(shape):
match shape:
case Point(x=0, y=0):
return "Point at origin"
case Point(x=x, y=y):
return f"Point at ({x}, {y})"
case Circle(center=Point(x=cx, y=cy), radius=r):
return f"Circle centered at ({cx}, {cy}) with radius {r}"
case _:
return "Unknown shape"
print(describe_shape(Point(0, 0))) # Point at origin
print(describe_shape(Point(3, 4))) # Point at (3, 4)
print(describe_shape(Circle(Point(1, 2), 5)))# Circle centered at (1, 2) with radius 5Observe o padrão de classe aninhado no caso Circle: Point(x=cx, y=cy) é correspondido dentro do padrão Circle. Os padrões podem ser compostos com profundidade arbitrária.
Para tipos embutidos como int, str, float e bool, você pode usar padrões posicionais com um único argumento:
def handle_input(value):
match value:
case (int() | float()) as number:
return f"Got a number: {number}"
case str() as text:
return f"Got text: {text!r}"
case _:
return "Unknown type"
print(handle_input(3.14)) # Got a number: 3.14
print(handle_input("hello")) # Got text: 'hello'
print(handle_input([1, 2])) # Unknown typeA palavra-chave as (o padrão AS) vincula o valor correspondido inteiro a um nome, mesmo após uma verificação de tipo.
Guardas
Uma guarda é uma condição if adicionada após um padrão. O case só corresponde quando o padrão se encaixa e a guarda avalia como True.
def classify_number(n):
match n:
case 0:
return "zero"
case x if x < 0:
return f"{x} is negative"
case x if x % 2 == 0:
return f"{x} is positive and even"
case x:
return f"{x} is positive and odd"
print(classify_number(0)) # zero
print(classify_number(-5)) # -5 is negative
print(classify_number(4)) # 4 is positive and even
print(classify_number(7)) # 7 is positive and oddAs guardas são avaliadas após a correspondência do padrão estrutural, então as variáveis capturadas ficam disponíveis dentro delas. Uma guarda que falha não impede que casos posteriores sejam testados.
O Padrão Curinga _
_ é o capturador universal. Ele corresponde a qualquer valor e não vincula nada (o valor é descartado). Deve ser o último case em um bloco match. Sem ele, um match que não encontrar nenhum case correspondente simplesmente não faz nada — nenhum erro é gerado.
def describe(value):
match value:
case 0:
return "zero"
case _:
return f"something else: {value!r}"
print(describe(0)) # zero
print(describe(99)) # something else: 99
print(describe("hi")) # something else: 'hi'_ também pode aparecer dentro de um padrão para ignorar partes específicas:
match point:
case (_, 0):
print("On the x-axis (x value doesn't matter)")
case (0, _):
print("On the y-axis (y value doesn't matter)")Combinando Padrões: Um Exemplo do Mundo Real
Os padrões acima se compõem. Aqui está um analisador de comandos de aventura de texto que combina padrões de sequência, guardas e o curinga:
def run_command(command):
match command.split():
case ["quit"]:
return "Quitting"
case ["go", direction] if direction in ("north", "south", "east", "west"):
return f"Going {direction}"
case ["go", direction]:
return f"Cannot go {direction!r} — try north, south, east, or west"
case ["get", item]:
return f"Picking up {item}"
case ["drop", item]:
return f"Dropping {item}"
case ["inventory"]:
return "Checking inventory"
case [verb, *args]:
return f"Unknown command {verb!r} with args {args}"
case []:
return "No command entered"
print(run_command("go north")) # Going north
print(run_command("go up")) # Cannot go 'up' — try north, south, east, or west
print(run_command("get sword")) # Picking up sword
print(run_command("drop torch")) # Dropping torch
print(run_command("quit")) # Quitting
print(run_command("")) # No command enteredLendo de cima para baixo, você pode entender imediatamente todos os comandos suportados — algo que exigiria muitas mais linhas de lógica if/elif para atingir a mesma clareza.
match vs. if/elif — Quando Usar Cada Um
| Cenário | Melhor escolha |
|---|---|
| Igualdade simples contra algumas constantes | Qualquer um; match é ligeiramente mais limpo |
| Correspondência na estrutura / forma dos dados | match — muito mais limpo |
| Desestruturar valores enquanto faz correspondência | match — não pode ser feito com if |
| Lógica envolvendo apenas condições calculadas | if/elif |
| Python 3.9 ou anterior | if/elif (match não disponível) |
| Expressar uma tabela de decisão claramente | match |
match não é substituto para toda cadeia de if. Quando todos os ramos verificam condições booleanas calculadas (por exemplo, if x > 10 and y < 5), uma cadeia de if/elif é mais natural. match brilha quando a condição diz respeito à forma dos dados.
Armadilhas Comuns
Nomes de constantes não são correspondidos pelo valor
Um nome simples em um case é sempre uma captura, nunca uma consulta:
STATUS_OK = 200
match response_code:
case STATUS_OK: # WRONG — this captures into STATUS_OK, not compares!
print("Success")Para comparar com uma constante nomeada, use um nome com ponto (http.HTTPStatus.OK) ou uma guarda:
match response_code:
case x if x == STATUS_OK:
print("Success")match não é exaustivo por padrão
Ao contrário de switch em alguns outros idiomas, um match que não tem case correspondente simplesmente não faz nada silenciosamente. Adicione um case _: se precisar de um handler garantido.
match requer Python 3.10+
Executar um bloco match no Python 3.9 ou anterior gera SyntaxError: invalid syntax. Verifique sua versão com python3 --version. Consulte o guia de introdução ao Python se precisar configurar um ambiente Python moderno.
Padrões não são expressões booleanas
Você não pode escrever case x > 5: — isso é uma guarda, não um padrão. A parte estrutural (case x) deve vir primeiro, seguida de uma expressão if guard_expression opcional.
Resumo
| Tipo de padrão | Exemplo de sintaxe | O que corresponde |
|---|---|---|
| Literal | case 42: | Valor exato |
| OR | case "yes" | "y": | Qualquer uma das alternativas |
| Curinga | case _: | Qualquer coisa (descarta o valor) |
| Captura | case x: | Qualquer coisa, vincula a x |
| Sequência | case [a, b, *rest]: | Uma sequência com pelo menos 2 elementos |
| Mapeamento | case {"key": val}: | Um dict contendo as chaves fornecidas |
| Classe | case Point(x=0, y=y): | Uma instância com atributos correspondentes |
| AS | case int() as n: | Corresponde e vincula o valor inteiro |
| Guarda | case x if x > 0: | Padrão + condição booleana extra |
Prática
Agora que você sabe como ramificar na estrutura de dados, explore loops for em Python para iterar sobre sequências, ou enums em Python para definir o tipo de constantes tipadas que funcionam bem com padrões de classe.