Skip to content

Python Match Case: Structural Pattern Matching Explicado (Python 3.10+)

Updated on

Desenvolvedores Python confiaram por décadas em cadeias de if-elif para lidar com lógica de ramificação. Verificar uma variável contra múltiplos valores, inspecionar a estrutura de dados ou fazer roteamento com base em tipos de objetos — tudo isso exige blocos condicionais verbosos que ficam mais difíceis de ler conforme os casos se multiplicam. Uma função que despacha dez tipos possíveis de comando vira um muro de instruções elif, em que a lógica real fica enterrada sob comparações repetitivas.

E isso não é apenas um problema de legibilidade. Cadeias profundas de if-elif são propensas a bugs sutis — uma condição fora do lugar, um caso esquecido ou um “fall-through” acidental que deixa dados errados seguirem adiante silenciosamente. Outras linguagens resolveram isso há anos com construções de pattern matching, mas o Python não tinha uma solução nativa até a versão 3.10.

O Python 3.10 introduziu a instrução match-case (PEP 634), trazendo structural pattern matching para a linguagem. Ela vai muito além de uma simples comparação de valores: você pode desempacotar sequências, casar formatos de dicionários, vincular variáveis, aplicar condições de guarda (guards) e despachar por tipos de classe — tudo em uma sintaxe limpa e declarativa. Este guia cobre todos os tipos de padrão com exemplos práticos.

📚

O que é Structural Pattern Matching?

Structural pattern matching permite comparar um valor (o “subject”) com uma série de padrões e executar o bloco de código do primeiro padrão que casar. Diferente de um switch statement em C ou Java, o match-case do Python não se limita a comparar valores — ele inspeciona a estrutura dos dados.

Sintaxe básica:

match subject:
    case pattern1:
        # code for pattern1
    case pattern2:
        # code for pattern2
    case _:
        # default case (wildcard)

Características principais:

  • Os padrões são avaliados de cima para baixo. O primeiro padrão que casar vence.
  • O padrão curinga _ casa com qualquer coisa e serve como caso padrão.
  • Padrões podem vincular variáveis, ou seja, partes do valor casado são capturadas em nomes que você pode usar dentro do bloco do case.
  • Não existe comportamento de fall-through. Apenas um bloco de case é executado.

Requisito de versão do Python: match-case exige Python 3.10 ou superior. Verifique sua versão com python --version. Se você estiver em uma versão mais antiga, precisa atualizar antes de usar este recurso.

Padrões Literais: Casando Valores Exatos

O uso mais simples de match-case compara um valor contra constantes literais:

def get_http_status_message(code):
    match code:
        case 200:
            return "OK"
        case 201:
            return "Created"
        case 301:
            return "Moved Permanently"
        case 400:
            return "Bad Request"
        case 403:
            return "Forbidden"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return f"Unknown status code: {code}"
 
print(get_http_status_message(404))  # Not Found
print(get_http_status_message(418))  # Unknown status code: 418

Padrões literais funcionam com inteiros, strings, booleanos e None:

def describe_value(value):
    match value:
        case True:
            return "Boolean true"
        case False:
            return "Boolean false"
        case None:
            return "None value"
        case "":
            return "Empty string"
        case 0:
            return "Zero"
        case _:
            return f"Other: {value}"

Importante: True, False e None são casados por identidade (como is), não por igualdade. Isso significa que match 1 não vai casar com case True mesmo que 1 == True seja True em Python.

Or-Patterns: Casando Múltiplos Valores

Use o operador pipe | para casar qualquer um de vários padrões em um único case:

def classify_day(day):
    match day.lower():
        case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
            return "Weekday"
        case "saturday" | "sunday":
            return "Weekend"
        case _:
            return "Invalid day"
 
print(classify_day("Saturday"))  # Weekend
print(classify_day("Wednesday")) # Weekday

Or-patterns funcionam com qualquer tipo de padrão, não apenas literais:

def categorize_error(code):
    match code:
        case 400 | 401 | 403 | 404 | 405:
            return "Client error"
        case 500 | 502 | 503 | 504:
            return "Server error"
        case _:
            return "Other"

Padrões de Captura de Variável

Padrões podem capturar partes do valor casado em variáveis. Um nome “nu” em um padrão atua como uma variável que se vincula ao valor que aparecer naquela posição:

def parse_command(command):
    match command.split():
        case ["quit"]:
            return "Exiting program"
        case ["hello", name]:
            return f"Hello, {name}!"
        case ["add", x, y]:
            return f"Sum: {int(x) + int(y)}"
        case _:
            return "Unknown command"
 
print(parse_command("hello Alice"))  # Hello, Alice!
print(parse_command("add 3 5"))      # Sum: 8
print(parse_command("quit"))         # Exiting program

Em case ["hello", name], a variável name captura qualquer string que apareça na segunda posição da lista. Isso é fundamentalmente diferente de casar literais — não existe uma variável chamada name sendo comparada. Em vez disso, o padrão cria um novo vínculo (binding).

A Armadilha de Captura vs Constante

Como nomes “nus” são padrões de captura, você não pode casar diretamente contra uma variável:

QUIT_COMMAND = "quit"
 
match user_input:
    case QUIT_COMMAND:  # This captures into QUIT_COMMAND, not compare!
        print("This always matches!")

Para casar contra uma constante, use nomes pontilhados (dotted names) ou valores literais:

class Commands:
    QUIT = "quit"
    HELP = "help"
 
match user_input:
    case Commands.QUIT:  # Dotted name: compares against the value
        print("Quitting")
    case Commands.HELP:
        print("Showing help")

Nomes pontilhados (contendo um .) são tratados como consulta de valor (value lookup), não como padrões de captura. Essa é uma decisão de design intencional na PEP 634.

Padrões de Sequência: Desestruturando Listas e Tuplas

Match-case é excelente para desestruturar sequências. Você pode casar o tamanho e o conteúdo de listas ou tuplas:

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})"
        case _:
            return "Not a valid point"
 
print(process_point((0, 0)))   # Origin
print(process_point((5, 0)))   # On x-axis at 5
print(process_point((3, 7)))   # Point at (3, 7)

Star Patterns para Sequências de Tamanho Variável

Use * para casar sequências de tamanho variável:

def analyze_sequence(seq):
    match seq:
        case []:
            return "Empty sequence"
        case [single]:
            return f"Single element: {single}"
        case [first, second]:
            return f"Pair: {first}, {second}"
        case [first, *middle, last]:
            return f"First: {first}, Last: {last}, Middle has {len(middle)} items"
 
print(analyze_sequence([]))              # Empty sequence
print(analyze_sequence([42]))            # Single element: 42
print(analyze_sequence([1, 2, 3, 4, 5])) # First: 1, Last: 5, Middle has 3 items

Padrões de Sequência Aninhados

Padrões podem ser aninhados para casar estruturas de dados complexas:

def process_matrix_row(row):
    match row:
        case [[a, b], [c, d]]:
            return f"2x2 block: {a}, {b}, {c}, {d}"
        case [first_row, *rest]:
            return f"First row: {first_row}, remaining rows: {len(rest)}"
 
print(process_matrix_row([[1, 2], [3, 4]]))
# 2x2 block: 1, 2, 3, 4

Padrões de Mapeamento: Casando Dicionários

Padrões de mapeamento casam objetos tipo dicionário verificando chaves específicas e, opcionalmente, capturando seus valores:

def handle_api_response(response):
    match response:
        case {"status": "success", "data": data}:
            return f"Success! Data: {data}"
        case {"status": "error", "message": msg}:
            return f"Error: {msg}"
        case {"status": "error", "code": code, "message": msg}:
            return f"Error {code}: {msg}"
        case {"status": status}:
            return f"Unknown status: {status}"
        case _:
            return "Invalid response format"
 
print(handle_api_response({"status": "success", "data": [1, 2, 3]}))
# Success! Data: [1, 2, 3]
 
print(handle_api_response({"status": "error", "message": "Not found"}))
# Error: Not found

Padrões de mapeamento casam se o dicionário contiver as chaves especificadas — chaves extras são ignoradas. Isso os torna ideais para trabalhar com dados JSON e respostas de API.

Use **rest para capturar as chaves restantes:

def extract_config(config):
    match config:
        case {"host": host, "port": port, **rest}:
            return f"Server: {host}:{port}, extra config: {rest}"
        case _:
            return "Missing required config"
 
print(extract_config({"host": "localhost", "port": 8080, "debug": True}))
# Server: localhost:8080, extra config: {'debug': True}

Guard Clauses: Adicionando Condições aos Padrões

Guards adicionam uma condição if que precisa ser verdadeira para o padrão casar. O padrão é verificado primeiro; se casar, a expressão de guarda é avaliada:

def classify_number(n):
    match n:
        case x if x < 0:
            return "Negative"
        case 0:
            return "Zero"
        case x if x % 2 == 0:
            return "Positive even"
        case x if x % 2 == 1:
            return "Positive odd"
 
print(classify_number(-5))  # Negative
print(classify_number(0))   # Zero
print(classify_number(4))   # Positive even
print(classify_number(7))   # Positive odd

Guards são essenciais quando o padrão sozinho não consegue expressar a condição completa de correspondência:

def validate_age(data):
    match data:
        case {"name": name, "age": age} if age < 0:
            return f"Invalid: {name} has negative age"
        case {"name": name, "age": age} if age < 18:
            return f"{name} is a minor (age {age})"
        case {"name": name, "age": age} if age >= 18:
            return f"{name} is an adult (age {age})"
        case _:
            return "Invalid data format"
 
print(validate_age({"name": "Alice", "age": 25}))
# Alice is an adult (age 25)

Padrões de Classe: Casando Tipos de Objetos

Padrões de classe verificam se um valor é uma instância de uma classe e, opcionalmente, desestruturam seus atributos:

from dataclasses import dataclass
 
@dataclass
class Point:
    x: float
    y: float
 
@dataclass
class Circle:
    center: Point
    radius: float
 
@dataclass
class Rectangle:
    top_left: 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) if r > 100:
            return f"Large circle at ({center.x}, {center.y})"
        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(top_left=tl, width=w, height=h):
            return f"Rectangle at ({tl.x}, {tl.y}), {w}x{h}"
        case _:
            return "Unknown shape"
 
print(describe_shape(Circle(Point(0, 0), 5)))
# Circle at origin with radius 5
 
print(describe_shape(Rectangle(Point(1, 2), 10, 10)))
# Square with side 10

Para classes sem __match_args__, use argumentos nomeados. Dataclasses e named tuples definem __match_args__ automaticamente, habilitando padrões posicionais:

@dataclass
class Color:
    r: int
    g: int
    b: int
 
def describe_color(color):
    match color:
        case Color(0, 0, 0):
            return "Black"
        case Color(255, 255, 255):
            return "White"
        case Color(r, 0, 0):
            return f"Red shade (r={r})"
        case Color(0, g, 0):
            return f"Green shade (g={g})"
        case Color(0, 0, b):
            return f"Blue shade (b={b})"
        case Color(r, g, b):
            return f"RGB({r}, {g}, {b})"
 
print(describe_color(Color(255, 0, 0)))   # Red shade (r=255)
print(describe_color(Color(128, 64, 32))) # RGB(128, 64, 32)

Correspondendo Tipos Built-in

Você pode casar tipos built-in para checagem de tipo:

def process_value(value):
    match value:
        case int(n):
            return f"Integer: {n}"
        case float(n):
            return f"Float: {n}"
        case str(s):
            return f"String: '{s}'"
        case list(items):
            return f"List with {len(items)} items"
        case dict(d):
            return f"Dict with keys: {list(d.keys())}"
        case _:
            return f"Other type: {type(value).__name__}"
 
print(process_value(42))          # Integer: 42
print(process_value("hello"))     # String: 'hello'
print(process_value([1, 2, 3]))   # List with 3 items

Match Case vs if-elif: Quando Usar Cada Um

Critériomatch-caseif-elif
Versão do PythonApenas 3.10+Todas as versões
Comparação de valoresLimpo, um valor por caseVerboso com repetição da variável
DesestruturaçãoNativa (sequências, dicts, objetos)Exige unpacking manual
Checagem de tipoPadrões de classe nativosExige chamadas isinstance()
Condições de guardaif após o padrãoif/elif padrão
Vinculação de variávelCaptura automáticaAtribuição manual
PerformanceDispatch otimizado de padrõesAvaliação sequencial
LegibilidadeMelhor para 4+ casos com estruturaMelhor para 1–3 condições simples
Fall-throughNão (por design)Possível com elif ausente

Quando match-case é claramente melhor:

# Parsing structured data -- match-case
def handle_event(event):
    match event:
        case {"type": "click", "x": x, "y": y}:
            process_click(x, y)
        case {"type": "keypress", "key": key, "modifiers": [*mods]}:
            process_key(key, mods)
        case {"type": "scroll", "delta": delta} if delta != 0:
            process_scroll(delta)

O equivalente com if-elif:

# Same logic with if-elif -- more verbose
def handle_event(event):
    if event.get("type") == "click" and "x" in event and "y" in event:
        process_click(event["x"], event["y"])
    elif event.get("type") == "keypress" and "key" in event and "modifiers" in event:
        process_key(event["key"], event["modifiers"])
    elif event.get("type") == "scroll" and event.get("delta", 0) != 0:
        process_scroll(event["delta"])

Quando if-elif ainda é melhor:

# Simple range checks -- if-elif is clearer
if temperature > 100:
    status = "boiling"
elif temperature > 50:
    status = "hot"
elif temperature > 20:
    status = "warm"
else:
    status = "cold"

Match-case não oferece suporte a comparações de faixa diretamente nos padrões. Você precisaria de guards para cada caso, o que elimina a vantagem de legibilidade.

Casos de Uso no Mundo Real

Parsing de Argumentos de Linha de Comando

import sys
 
def main():
    match sys.argv[1:]:
        case ["help"]:
            print("Available commands: help, version, run, test")
        case ["version"]:
            print("v1.0.0")
        case ["run", filename]:
            print(f"Running {filename}")
        case ["run", filename, "--verbose"]:
            print(f"Running {filename} with verbose output")
        case ["test", *test_files] if test_files:
            print(f"Testing: {', '.join(test_files)}")
        case ["test"]:
            print("Running all tests")
        case [unknown, *_]:
            print(f"Unknown command: {unknown}")
        case []:
            print("No command provided. Use 'help' for usage.")

Implementação de Máquina de Estados

from dataclasses import dataclass
from typing import Optional
 
@dataclass
class State:
    name: str
    data: Optional[dict] = None
 
def transition(state, event):
    match (state.name, event):
        case ("idle", "start"):
            return State("loading", {"progress": 0})
        case ("loading", "progress"):
            progress = state.data.get("progress", 0) + 25
            if progress >= 100:
                return State("ready", {"progress": 100})
            return State("loading", {"progress": progress})
        case ("loading", "cancel"):
            return State("idle")
        case ("ready", "process"):
            return State("processing", state.data)
        case ("processing", "complete"):
            return State("done", {"result": "success"})
        case ("processing", "error"):
            return State("error", {"message": "Processing failed"})
        case (_, "reset"):
            return State("idle")
        case (current, unknown_event):
            print(f"No transition from '{current}' on '{unknown_event}'")
            return state
 
# Usage
state = State("idle")
for event in ["start", "progress", "progress", "progress", "progress", "process", "complete"]:
    state = transition(state, event)
    print(f"Event: {event} -> State: {state.name}, Data: {state.data}")

Handler de Resposta de API JSON

def process_api_result(response):
    match response:
        case {"data": {"users": [*users]}, "meta": {"total": total}}:
            print(f"Found {total} users, received {len(users)}")
            for user in users:
                match user:
                    case {"name": name, "role": "admin"}:
                        print(f"  Admin: {name}")
                    case {"name": name, "role": role}:
                        print(f"  {role.title()}: {name}")
 
        case {"data": {"users": []}}:
            print("No users found")
 
        case {"error": {"code": code, "message": msg}} if code >= 500:
            print(f"Server error ({code}): {msg}")
            raise RuntimeError(msg)
 
        case {"error": {"code": code, "message": msg}}:
            print(f"Client error ({code}): {msg}")
 
        case _:
            print(f"Unexpected response format: {type(response)}")
 
# Example
process_api_result({
    "data": {"users": [
        {"name": "Alice", "role": "admin"},
        {"name": "Bob", "role": "editor"}
    ]},
    "meta": {"total": 2}
})

Parser de Arquivo de Configuração

def apply_config(settings):
    for key, value in settings.items():
        match (key, value):
            case ("database", {"host": host, "port": int(port), "name": db}):
                print(f"DB connection: {host}:{port}/{db}")
            case ("database", _):
                raise ValueError("Invalid database config: need host, port, name")
            case ("logging", {"level": level}) if level in ("DEBUG", "INFO", "WARNING", "ERROR"):
                print(f"Log level set to {level}")
            case ("logging", {"level": level}):
                raise ValueError(f"Invalid log level: {level}")
            case ("features", {"enabled": list(features)}):
                print(f"Enabled features: {', '.join(features)}")
            case ("features", {"enabled": _}):
                raise TypeError("Features must be a list")
            case (key, _):
                print(f"Unknown config key: {key}")
 
apply_config({
    "database": {"host": "localhost", "port": 5432, "name": "mydb"},
    "logging": {"level": "INFO"},
    "features": {"enabled": ["auth", "cache", "metrics"]}
})

Pipeline de Transformação de Dados

def transform_record(record):
    """Transform raw data records into standardized format."""
    match record:
        case {"timestamp": ts, "value": float(v) | int(v), "unit": str(unit)}:
            return {"time": ts, "measurement": float(v), "unit": unit.lower()}
 
        case {"timestamp": ts, "value": str(v), "unit": str(unit)}:
            try:
                return {"time": ts, "measurement": float(v), "unit": unit.lower()}
            except ValueError:
                return {"time": ts, "measurement": None, "unit": unit.lower(), "error": f"Cannot parse: {v}"}
 
        case {"timestamp": ts, "value": v}:
            return {"time": ts, "measurement": float(v), "unit": "unknown"}
 
        case {"values": [*values]} if values:
            return [transform_record({"timestamp": None, "value": v, "unit": "batch"}) for v in values]
 
        case _:
            return {"error": f"Unrecognized format: {record}"}
 
# Examples
print(transform_record({"timestamp": "2026-01-01", "value": 42.5, "unit": "Celsius"}))
print(transform_record({"timestamp": "2026-01-01", "value": "98.6", "unit": "F"}))

Técnicas Avançadas de Padrões

Padrão AS: Vinculando o Match Inteiro

Use as para capturar o valor inteiro casado enquanto também o desestrutura:

def process_item(item):
    match item:
        case {"name": str(name), "price": float(price)} as product if price > 100:
            print(f"Premium product: {product}")
        case {"name": str(name), "price": float(price)} as product:
            print(f"Standard product: {product}")

Combinando Tipos de Padrão

Os padrões se compõem naturalmente. Você pode misturar padrões de sequência, mapeamento e classe:

@dataclass
class Order:
    items: list
    customer: dict
 
def process_order(order):
    match order:
        case Order(items=[single_item], customer={"vip": True}):
            print(f"VIP single-item order: {single_item}")
        case Order(items=[_, _, *rest], customer={"name": name}) if len(rest) > 0:
            print(f"{name} ordered {2 + len(rest)} items")
        case Order(items=[], customer=_):
            print("Empty order")

Usando Match-Case em Jupyter Notebooks

Pattern matching é particularmente útil em fluxos de trabalho de análise de dados, onde você precisa lidar com diferentes formatos de dados ou respostas de API. Ao explorar datasets interativamente em Jupyter notebooks, match-case oferece uma forma limpa de lidar com a variedade de “shapes” de dados que você encontra.

def classify_cell_output(output):
    """Classify Jupyter cell output by type."""
    match output:
        case {"output_type": "stream", "text": text}:
            return f"Text output: {len(text)} chars"
        case {"output_type": "error", "ename": name, "evalue": value}:
            return f"Error: {name}: {value}"
        case {"output_type": "display_data", "data": {"image/png": _}}:
            return "Image output"
        case {"output_type": "execute_result", "data": {"text/html": html}}:
            return "HTML table output"
        case _:
            return "Unknown output type"

Para data scientists que querem assistência de AI enquanto trabalham com pattern matching e outros recursos do Python 3.10+ em notebooks, RunCell (opens in a new tab) oferece um AI agent direto no Jupyter que pode ajudar a escrever, depurar e otimizar instruções match-case com base nas suas estruturas de dados reais.

Erros Comuns e Como Evitá-los

Erro 1: Usar match-case em Python < 3.10

# This fails with SyntaxError on Python 3.9 and earlier
match value:
    case 1:
        print("one")

Correção: Verifique sys.version_info >= (3, 10) ou atualize o Python.

Erro 2: Tratar nomes em case como comparações

expected = 200
 
match status_code:
    case expected:  # WRONG: creates a new variable 'expected'
        print("OK")

Correção: Use um nome pontilhado, um literal ou um guard:

match status_code:
    case code if code == expected:
        print("OK")

Erro 3: Esquecer o caso curinga

Sem case _, valores não casados não fazem nada silenciosamente:

match command:
    case "start":
        run()
    case "stop":
        halt()
    # If command is "pause", nothing happens -- no error, no warning

Correção: Sempre inclua um caso curinga para tratamento explícito:

match command:
    case "start":
        run()
    case "stop":
        halt()
    case _:
        raise ValueError(f"Unknown command: {command}")

Erro 4: Padrões dependentes da ordem sem especificidade

Os padrões são checados de cima para baixo. Um padrão amplo antes de um específico “sombreia” o específico:

# WRONG: the first case always matches
match point:
    case (x, y):       # Captures everything
        print("generic")
    case (0, 0):       # Never reached
        print("origin")

Correção: Coloque padrões específicos primeiro:

match point:
    case (0, 0):
        print("origin")
    case (x, y):
        print(f"point at ({x}, {y})")

Considerações de Performance

Match-case é compilado para bytecode eficiente. Para correspondência literal simples, o compilador do Python o otimiza de forma semelhante a consultas em dicionário. Para padrões estruturais, ele gera uma árvore de decisão que evita checagens redundantes.

Benchmarks mostram que match-case tem desempenho comparável a cadeias if-elif para números pequenos de casos (menos de 5). Para tabelas de dispatch maiores (10+ casos), match-case pode ser mais rápido devido à otimização de bytecode, especialmente com padrões literais.

Ainda assim, pattern matching não substitui dispatch via dicionário quando você só precisa mapear valores para funções:

# For simple value-to-action mapping, a dict is cleaner and faster
handlers = {
    "click": handle_click,
    "scroll": handle_scroll,
    "keypress": handle_keypress,
}
 
handler = handlers.get(event_type, handle_unknown)
handler(event_data)

Use match-case quando você precisar de inspeção estrutural, desestruturação ou guard conditions — coisas que uma consulta em dicionário não consegue expressar.

FAQ

De que versão do Python eu preciso para match-case?

Python 3.10 ou superior é necessário para a instrução match-case. Ela foi introduzida na PEP 634 como parte do lançamento do Python 3.10 em outubro de 2021. Tentar usar match-case no Python 3.9 ou anterior resulta em SyntaxError. Você pode verificar sua versão executando python --version no seu terminal.

O match-case do Python é o mesmo que switch-case em outras linguagens?

Não. O match-case do Python é structural pattern matching, que é mais poderoso do que um switch-case tradicional. Enquanto switch-case em C ou Java apenas compara valores, o match-case do Python pode desestruturar sequências e dicionários, casar instâncias de classe por atributos, vincular variáveis a partir de dados casados e aplicar guard conditions. Ele é mais próximo do pattern matching em Rust, Scala ou Haskell.

Match-case tem fall-through como o switch de C?

Não. O match-case do Python executa apenas o primeiro bloco de case que casar e então sai da instrução match. Não há fall-through e não é necessário usar break. Se você quiser que múltiplos padrões executem o mesmo código, use or-patterns com o operador pipe: case "a" | "b" | "c": casará com qualquer um desses valores.

Posso usar match-case com expressões regulares?

Não diretamente. Os padrões em match-case são estruturais, não baseados em regex. Porém, você pode usar guard clauses para aplicar regex: case str(s) if re.match(r"pattern", s): combina checagem estrutural de tipo com validação via regex na guarda.

Como match-case lida com valores None?

None é casado como um padrão literal usando case None:. Como None é um singleton em Python, o match usa comparação por identidade (equivalente a is None). Isso significa que case None: casará apenas com o objeto None real, e não com outros valores “falsy” como 0, False ou strings vazias.

O que acontece se nenhum case casar?

Se nenhum case casar e não houver um padrão curinga (case _:), a instrução match não faz nada silenciosamente — a execução continua após o bloco match. Nenhuma exceção é levantada. Por isso, é uma boa prática sempre incluir um caso curinga, seja para fornecer uma ação padrão, seja para levantar uma exceção para valores inesperados.

Conclusão

A instrução match-case do Python traz structural pattern matching para uma linguagem que por muito tempo dependeu de cadeias if-elif para lógica de ramificação complexa. Ela oferece uma forma declarativa de inspecionar “shapes” de dados, desestruturar sequências e dicionários, despachar por tipos de objetos e vincular variáveis — tudo em uma sintaxe que lê com mais clareza do que o código condicional equivalente.

O insight principal é que match-case não é apenas um switch statement. É uma ferramenta para trabalhar com dados estruturados: parsing de comandos, tratamento de respostas de API, implementação de máquinas de estado e roteamento com base em tipos de objetos. Quando sua lógica de ramificação envolve checar o formato dos dados em vez de comparações simples de valores, match-case produz código que é ao mesmo tempo mais curto e mais fácil de manter.

Comece com padrões literais simples e, então, gradualmente incorpore desestruturação de sequência, padrões de mapeamento e padrões de classe conforme seus casos de uso exigirem. Sempre inclua um caso curinga para tratamento explícito de entradas inesperadas, coloque padrões específicos antes de padrões gerais e lembre-se de que nomes “nus” capturam valores em vez de compará-los.

Para exploração interativa de padrões match-case com seus próprios dados, Jupyter notebooks oferecem um ambiente ideal para testar padrões incrementalmente. RunCell (opens in a new tab) melhora esse fluxo de trabalho com assistência de AI que entende recursos do Python 3.10+ e pode sugerir estruturas de padrões com base nos “shapes” dos seus dados.

📚