Python Switch Case: explicando a instrução match-case
Atualizado em
Você está escrevendo um processador de comandos em Python. A função recebe uma string como "quit", "save", "load" ou "help" e precisa executar uma lógica diferente para cada comando. Em JavaScript ou C, você usaria um statement switch. Em Python, você escreve uma cadeia de blocos if/elif/else — cinco linhas para dois comandos, quinze para seis, trinta para doze. A função cresce e vira uma parede de comparações, difícil de ler, fácil de ordenar errado e trabalhosa de विस्तारir.
If your search query is python switch case, start here. For the deeper structural-pattern-matching reference, see Python Match Case. For a simpler dictionary-dispatch overview, see Python Switch Case.
Isso foi uma limitação real do Python por mais de 30 anos. Todas as outras linguagens principais tinham um constructo switch ou case, mas o Python dependia totalmente de cadeias if/elif e dispatch por dicionário. Guido van Rossum rejeitou várias propostas de switch-case, argumentando que if/elif era suficiente.
Isso mudou no Python 3.10 com PEP 634 (opens in a new tab), que introduziu o statement match-case — a resposta do Python ao switch-case, mas muito mais poderosa. Em vez de apenas comparar valores, ele pode desmontar objetos, casar padrões, vincular variáveis e aplicar condições. É structural pattern matching, não apenas uma substituição de switch.
Sintaxe Rápida: match-case em 30 Segundos
def handle_command(command):
match command:
case "quit":
print("Exiting program")
case "save":
print("Saving file")
case "help":
print("Showing help")
case _:
print(f"Unknown command: {command}")
handle_command("save") # Saving file
handle_command("hello") # Unknown command: helloA keyword match avalia a expressão sujeita uma única vez. Cada case define um padrão. O Python testa os padrões de cima para baixo e executa o primeiro que corresponder. O curinga _ é o caso padrão — ele casa com qualquer coisa.
match-case vs switch-case: Principais Diferenças
Se você vem de C, Java, JavaScript ou Go, aqui estão as diferenças críticas:
| Feature | Python match-case | C/Java switch-case |
|---|---|---|
| Fall-through | Não (cada case é independente) | Sim (exige break) |
| Pattern types | Literals, sequences, mappings, classes, OR, guards | Mostly literals only |
| Destructuring | Built-in (bind variables from matched structure) | Not supported |
| Variable binding | Captures values from matched pattern | Not supported |
| Default case | case _: | default: |
| Expression/statement | Statement (no return value) | Statement |
| Minimum Python | 3.10+ | N/A |
A diferença mais importante: sem fall-through. Cada bloco de case é isolado. Você nunca precisa de um statement break, e nunca executa acidentalmente o próximo case.
Tipos de Padrão: O que match-case Pode Fazer
O match-case do Python suporta sete tipos de padrão. Isso é o que o torna fundamentalmente mais poderoso do que um switch tradicional.
1. Literal Patterns
Casa valores exatos — strings, números, booleanos, None:
def classify_status(code):
match code:
case 200:
return "OK"
case 301:
return "Moved Permanently"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return f"Unknown status: {code}"
print(classify_status(404)) # Not Found2. OR Patterns (Múltiplos Valores)
Use o operador | para casar vários valores em um único case:
def classify_http_status(code):
match code:
case 200 | 201 | 204:
return "Success"
case 301 | 302 | 307 | 308:
return "Redirect"
case 400 | 401 | 403 | 404:
return "Client Error"
case 500 | 502 | 503:
return "Server Error"
case _:
return "Unknown"Isso substitui o padrão comum if code in (200, 201, 204):.
3. Capture Patterns (Binding de Variável)
Um nome solto em um case captura o valor casado em uma variável:
def process_value(data):
match data:
case 0:
print("Zero")
case value:
print(f"Got non-zero value: {value}")
process_value(42) # Got non-zero value: 42Armadilha importante: Um nome solto sempre captura — ele não compara contra uma variável existente. Este é o erro mais comum:
# WRONG — this does NOT compare against expected_code
expected_code = 200
match response_code:
case expected_code: # This captures response_code into expected_code!
print("Match")
# RIGHT — use a guard or literal
match response_code:
case code if code == expected_code:
print("Match")Para casar contra constantes, use nomes com ponto (como http.HTTPStatus.OK) ou guard clauses.
4. Sequence Patterns (Lists and Tuples)
Desmonte listas e tuplas diretamente no padrão:
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((3, 0))) # On x-axis at 3
print(process_point((2, 5))) # Point at (2, 5)Você pode usar *rest para capturar os elementos restantes:
def first_and_rest(items):
match items:
case []:
return "Empty list"
case [single]:
return f"Single item: {single}"
case [first, *rest]:
return f"First: {first}, remaining: {rest}"
print(first_and_rest([1, 2, 3, 4])) # First: 1, remaining: [2, 3, 4]5. Mapping Patterns (Dictionaries)
Casa contra chaves de dicionário e desmonta valores:
def handle_event(event):
match event:
case {"type": "click", "x": x, "y": y}:
print(f"Click at ({x}, {y})")
case {"type": "keypress", "key": key}:
print(f"Key pressed: {key}")
case {"type": "scroll", "direction": direction}:
print(f"Scroll {direction}")
case _:
print("Unknown event")
handle_event({"type": "click", "x": 100, "y": 200})
# Click at (100, 200)
handle_event({"type": "keypress", "key": "Enter", "modifiers": ["Ctrl"]})
# Key pressed: Enter (extra keys are ignored)Mapping patterns casam se as chaves especificadas estiverem presentes — chaves extras no dicionário são ignoradas silenciosamente. Isso os torna ideais para processar JSON ou respostas de API.
6. Class Patterns
Casa instâncias de classe e extrai atributos:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Circle:
center: Point
radius: float
@dataclass
class Rectangle:
corner: Point
width: float
height: float
def describe_shape(shape):
match shape:
case Circle(center=Point(x=0, y=0), radius=r):
return f"Circle at origin with radius {r}"
case Circle(center=center, radius=r):
return f"Circle at ({center.x}, {center.y}) with radius {r}"
case Rectangle(width=w, height=h) if w == h:
return f"Square with side {w}"
case Rectangle(width=w, height=h):
return f"Rectangle {w}x{h}"
print(describe_shape(Circle(Point(0, 0), 5)))
# Circle at origin with radius 5
print(describe_shape(Rectangle(Point(1, 1), 4, 4)))
# Square with side 47. Guard Clauses
Adicione condições if a qualquer padrão para filtragem adicional:
def classify_number(n):
match n:
case n if n < 0:
return "Negative"
case 0:
return "Zero"
case n if n % 2 == 0:
return "Positive even"
case _:
return "Positive odd"
print(classify_number(-5)) # Negative
print(classify_number(4)) # Positive even
print(classify_number(7)) # Positive oddOs guards permitem adicionar condições arbitrárias além do que os padrões conseguem expressar. O padrão é casado primeiro, e então a condição do guard é avaliada.
Exemplo Prático: Parser de Resposta JSON de API
Um dos usos mais práticos de match-case é analisar respostas de API ou dados JSON:
import json
def process_api_response(response: dict) -> str:
match response:
case {"status": "success", "data": {"users": [*users]}}:
return f"Found {len(users)} users"
case {"status": "success", "data": {"user": {"name": name, "email": email}}}:
return f"User: {name} ({email})"
case {"status": "error", "code": 401}:
return "Authentication required"
case {"status": "error", "code": code, "message": msg}:
return f"Error {code}: {msg}"
case {"status": "error"}:
return "Unknown error"
case _:
return "Unrecognized response format"
# Test with different responses
print(process_api_response({
"status": "success",
"data": {"users": ["Alice", "Bob", "Charlie"]}
}))
# Found 3 users
print(process_api_response({
"status": "error",
"code": 429,
"message": "Rate limit exceeded"
}))
# Error 429: Rate limit exceededIsso substitui cadeias profundas de if/elif com checagens isinstance() e chamadas de dicionário .get(). A versão com match-case declara diretamente a estrutura esperada.
Exemplo Prático: Parser de Argumentos de Linha de Comando
import sys
def parse_args(args: list[str]):
match args:
case [program]:
print(f"Usage: {program} <command> [options]")
case [_, "init", project_name]:
print(f"Initializing project: {project_name}")
case [_, "init"]:
print("Error: project name required")
case [_, "build", "--release"]:
print("Building in release mode")
case [_, "build"]:
print("Building in debug mode")
case [_, "test", *test_files] if test_files:
print(f"Running tests: {', '.join(test_files)}")
case [_, "test"]:
print("Running all tests")
case [_, unknown, *_]:
print(f"Unknown command: {unknown}")
parse_args(["app", "init", "my-project"]) # Initializing project: my-project
parse_args(["app", "test", "a.py", "b.py"]) # Running tests: a.py, b.py
parse_args(["app", "build", "--release"]) # Building in release modeAlternativas Antes do 3.10: Switch-Case Sem match-case
Se você estiver usando Python 3.9 ou anterior, ou se match-case for exagero para um simple value matching, aqui estão as alternativas consagradas.
if/elif/else Chains
A abordagem mais direta:
def handle_command(command):
if command == "quit":
print("Exiting program")
elif command == "save":
print("Saving file")
elif command == "load":
print("Loading file")
elif command == "help":
print("Showing help")
else:
print(f"Unknown command: {command}")When to use: When you have fewer than 5-6 cases and the logic is simple.
Dictionary Dispatch
Mapeie valores para funções usando um dicionário:
def cmd_quit():
print("Exiting program")
def cmd_save():
print("Saving file")
def cmd_help():
print("Showing help")
commands = {
"quit": cmd_quit,
"save": cmd_save,
"help": cmd_help,
}
def handle_command(command):
action = commands.get(command)
if action:
action()
else:
print(f"Unknown command: {command}")When to use: When mapping values to simple actions, especially with many cases. O(1) lookup vs O(n) for if/elif.
Dictionary Dispatch with Arguments
def calculate(operation, a, b):
ops = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b if b != 0 else float("inf"),
}
func = ops.get(operation)
if func is None:
raise ValueError(f"Unknown operation: {operation}")
return func(a, b)
print(calculate("+", 10, 3)) # 13
print(calculate("/", 10, 0)) # infEnum-Based Dispatch
Para código tipado e autoexplicativo:
from enum import Enum
class Direction(Enum):
NORTH = "north"
SOUTH = "south"
EAST = "east"
WEST = "west"
def move(direction: Direction, x: int, y: int) -> tuple[int, int]:
offsets = {
Direction.NORTH: (0, 1),
Direction.SOUTH: (0, -1),
Direction.EAST: (1, 0),
Direction.WEST: (-1, 0),
}
dx, dy = offsets[direction]
return (x + dx, y + dy)
print(move(Direction.NORTH, 0, 0)) # (0, 1)Comparação: Qual Abordagem Usar
| Approach | Python Version | Best For | Complexity | Performance |
|---|---|---|---|---|
match-case | 3.10+ | Complex patterns, destructuring, type matching | High expressiveness | Comparable to if/elif |
if/elif/else | All | Simple value comparisons, < 6 cases | Simple | O(n) linear scan |
| Dict dispatch | All | Many cases, value → function mapping | Medium | O(1) lookup |
| Dict + lambda | All | Many cases, value → expression mapping | Medium | O(1) lookup |
| Enum dispatch | All | Typed constants, exhaustive matching | Medium | O(1) lookup |
Rule of thumb:
- Use match-case when you need destructuring, type matching, or complex patterns
- Use if/elif for simple comparisons with few branches
- Use dict dispatch for many simple value-to-action mappings
- Use enum dispatch when the set of values is fixed and typed
Padrões Comuns e Receitas
Type-Based Dispatch
def serialize(value):
match value:
case bool(): # Must come before int — bool is a subclass of int
return "true" if value else "false"
case int() | float():
return str(value)
case str():
return f'"{value}"'
case list():
items = ", ".join(serialize(v) for v in value)
return f"[{items}]"
case dict():
pairs = ", ".join(
f'{serialize(k)}: {serialize(v)}' for k, v in value.items()
)
return "{" + pairs + "}"
case None:
return "null"
case _:
raise TypeError(f"Cannot serialize {type(value)}")
print(serialize({"name": "Alice", "scores": [95, 87, 92], "active": True}))
# {"name": "Alice", "scores": [95, 87, 92], "active": true}Important: Put bool() before int() because True and False are instances of int in Python.
Nested Pattern Matching
def evaluate(expr):
"""Simple math expression evaluator."""
match expr:
case int(n) | float(n):
return n
case ("+", left, right):
return evaluate(left) + evaluate(right)
case ("-", left, right):
return evaluate(left) - evaluate(right)
case ("*", left, right):
return evaluate(left) * evaluate(right)
case ("/", left, right):
divisor = evaluate(right)
if divisor == 0:
raise ZeroDivisionError("Division by zero")
return evaluate(left) / divisor
case ("neg", operand):
return -evaluate(operand)
case _:
raise ValueError(f"Invalid expression: {expr}")
# (3 + 4) * 2
result = evaluate(("*", ("+", 3, 4), 2))
print(result) # 14State Machine with match-case
def tokenize(text):
"""Simple tokenizer using match-case as a state machine."""
tokens = []
i = 0
while i < len(text):
match text[i]:
case ' ' | '\t' | '\n':
i += 1 # Skip whitespace
case '+' | '-' | '*' | '/':
tokens.append(("OP", text[i]))
i += 1
case '(' | ')':
tokens.append(("PAREN", text[i]))
i += 1
case c if c.isdigit():
j = i
while j < len(text) and text[j].isdigit():
j += 1
tokens.append(("NUM", int(text[i:j])))
i = j
case c if c.isalpha():
j = i
while j < len(text) and text[j].isalnum():
j += 1
tokens.append(("ID", text[i:j]))
i = j
case c:
raise SyntaxError(f"Unexpected character: {c}")
return tokens
print(tokenize("x + 42 * (y - 3)"))
# [('ID', 'x'), ('OP', '+'), ('NUM', 42), ('OP', '*'), ('PAREN', '('),
# ('ID', 'y'), ('OP', '-'), ('NUM', 3), ('PAREN', ')')]Performance: match-case vs if/elif vs dict
Para simple value matching, todas as três abordagens são rápidas o suficiente para que a diferença não importe na prática. Aqui estão resultados de benchmark para matching contra 10 string values:
import timeit
commands = ["cmd_0", "cmd_1", "cmd_2", "cmd_3", "cmd_4",
"cmd_5", "cmd_6", "cmd_7", "cmd_8", "cmd_9"]
# if/elif approach
def if_elif_dispatch(cmd):
if cmd == "cmd_0": return 0
elif cmd == "cmd_1": return 1
elif cmd == "cmd_2": return 2
elif cmd == "cmd_3": return 3
elif cmd == "cmd_4": return 4
elif cmd == "cmd_5": return 5
elif cmd == "cmd_6": return 6
elif cmd == "cmd_7": return 7
elif cmd == "cmd_8": return 8
elif cmd == "cmd_9": return 9
else: return -1
# dict dispatch approach
dispatch_dict = {f"cmd_{i}": i for i in range(10)}
def dict_dispatch(cmd):
return dispatch_dict.get(cmd, -1)
# match-case approach
def match_dispatch(cmd):
match cmd:
case "cmd_0": return 0
case "cmd_1": return 1
case "cmd_2": return 2
case "cmd_3": return 3
case "cmd_4": return 4
case "cmd_5": return 5
case "cmd_6": return 6
case "cmd_7": return 7
case "cmd_8": return 8
case "cmd_9": return 9
case _: return -1| Approach | Best case (first match) | Worst case (last match) | Miss (no match) |
|---|---|---|---|
| if/elif | ~80ns | ~400ns | ~420ns |
| dict dispatch | ~60ns | ~60ns | ~65ns |
| match-case | ~90ns | ~450ns | ~470ns |
Key takeaway: Dictionary dispatch has O(1) constant time regardless of position. Both if/elif and match-case are O(n) linear scan. For fewer than ~20 cases, the difference is negligible. Choose based on readability, not performance.
Erros Comuns e Como Evitá-los
Erro 1: Usar match-case Antes do Python 3.10
# This causes SyntaxError in Python 3.9 and earlier
match command: # SyntaxError: invalid syntax
case "quit":
passVerifique sua versão do Python: python --version. Se você estiver no 3.9 ou anterior, use if/elif ou dictionary dispatch.
Erro 2: Comparar Contra Variáveis
# WRONG — captures, does NOT compare
STATUS_OK = 200
match response_code:
case STATUS_OK: # This always matches and overwrites STATUS_OK!
print("OK")
# RIGHT — use dotted name
class Status:
OK = 200
NOT_FOUND = 404
match response_code:
case Status.OK:
print("OK")
case Status.NOT_FOUND:
print("Not Found")
# RIGHT — use a guard
match response_code:
case code if code == STATUS_OK:
print("OK")Erro 3: Esquecer o Wildcard Default
# DANGEROUS — unmatched values silently do nothing
match command:
case "save":
save_file()
case "load":
load_file()
# No default — "delete" silently falls through with no action
# SAFE — always include a default case
match command:
case "save":
save_file()
case "load":
load_file()
case _:
raise ValueError(f"Unknown command: {command}")Erro 4: Ordem Errada com Padrões Sobrepostos
# WRONG — the first case catches everything
match value:
case x: # Captures ANY value — always matches!
print(f"Got: {x}")
case 42: # Never reached
print("The answer")
# RIGHT — specific patterns first, general last
match value:
case 42:
print("The answer")
case x:
print(f"Got: {x}")Usando match-case em Data Science
Se você trabalha com pipelines de processamento de dados, match-case é útil para lidar com diferentes formatos de dados e operações de limpeza:
import csv
from pathlib import Path
def load_data(source):
"""Load data from different source types."""
match source:
case str() as path if path.endswith(".csv"):
with open(path) as f:
return list(csv.DictReader(f))
case str() as path if path.endswith(".json"):
import json
with open(path) as f:
return json.load(f)
case list() as records:
return records
case dict() as single_record:
return [single_record]
case _:
raise TypeError(f"Unsupported data source: {type(source)}")Para exploração interativa de dados, ferramentas como PyGWalker (opens in a new tab) permitem visualizar DataFrames diretamente sem escrever código de plot — útil quando você quer inspecionar rapidamente os dados produzidos pelo seu pipeline.
Se você estiver construindo scripts de processamento de dados que usam match-case para branching complexo, RunCell (opens in a new tab) fornece um ambiente Jupyter com IA onde você pode testar pattern matching de forma interativa com amostras de dados reais.
match-case com Dataclasses e Named Tuples
O match-case do Python funciona especialmente bem com dataclasses e named tuples:
from dataclasses import dataclass
from typing import Optional
@dataclass
class LogEntry:
level: str
message: str
error: Optional[Exception] = None
def handle_log(entry: LogEntry):
match entry:
case LogEntry(level="CRITICAL", error=err) if err is not None:
send_alert(f"CRITICAL with error: {err}")
restart_service()
case LogEntry(level="CRITICAL", message=msg):
send_alert(f"CRITICAL: {msg}")
case LogEntry(level="ERROR", error=err) if err is not None:
log_to_file(f"ERROR: {err}")
case LogEntry(level="WARNING" | "ERROR", message=msg):
log_to_file(msg)
case LogEntry(level="INFO" | "DEBUG"):
pass # Ignore low-priority logsPerguntas Frequentes
Does Python have a switch statement?
Python 3.10+ has the match-case statement, which is Python's version of switch-case. It goes beyond traditional switch with structural pattern matching — you can destructure objects, match patterns, and bind variables. For Python 3.9 and earlier, use if/elif/else chains or dictionary dispatch as alternatives.
What is the difference between match-case and switch-case?
Python's match-case has no fall-through (no break needed), supports pattern matching (sequences, mappings, classes), can destructure and bind variables from matched patterns, and supports guard clauses with if conditions. Traditional switch-case in C/Java only matches literal values.
Is match-case faster than if/elif in Python?
For simple value matching, they perform similarly — both scan linearly. Dictionary dispatch is faster (O(1)) for many cases. The performance difference is negligible for fewer than 20 cases. Choose based on readability and pattern complexity, not performance.
Can I use match-case with Python 3.9?
No. The match-case statement requires Python 3.10 or later. For earlier versions, use if/elif/else chains or dictionary dispatch.
Why does my match-case variable comparison not work?
A bare name like case x: captures any value into x — it does not compare against an existing variable. To compare against a variable, use a guard clause (case val if val == x:) or dotted names (case MyClass.CONSTANT:).
What is the underscore _ in match-case?
The _ wildcard pattern matches any value without binding it to a variable. It is the default case — like default: in switch statements. Always place it last.
Guias Relacionados
- Python Try Except: Error Handling Guide
- Python Type Hints: Complete Tutorial
- Python Decorators Explained
- Python Collections Module Guide
- Python Enumerate Function
- Python Assert Statement