Python 3.10 introdujo el match statement, que va mucho más allá de un simple switch/case. Es pattern matching estructural: puede desempaquetar datos, comprobar tipos, extraer valores y añadir condiciones adicionales, todo en una sintaxis limpia que elimina cadenas de if/elif complejas.
La sintaxis básica
# Comparación directa de valores (como switch/case clásico)
def dia_tipo(dia: str) -> str:
match dia.lower():
case "lunes" | "martes" | "miércoles" | "jueves" | "viernes":
return "laborable"
case "sábado" | "domingo":
return "fin de semana"
case _: # wildcard: coincide con cualquier cosa
return "día desconocido"
print(dia_tipo("Lunes")) # laborable
print(dia_tipo("Domingo")) # fin de semana
print(dia_tipo("festivo")) # día desconocido
Pattern matching de secuencias
def parsear_comando(args: list[str]) -> str:
match args:
case []:
return "sin argumentos"
case [comando]:
return f"comando simple: {comando}"
case [comando, *resto]:
return f"comando '{comando}' con {len(resto)} argumentos: {resto}"
print(parsear_comando([])) # sin argumentos
print(parsear_comando(["listar"])) # comando simple: listar
print(parsear_comando(["copiar", "a.txt", "b.txt"])) # comando 'copiar' con 2 argumentos
# Desempaquetar coordenadas
def describir_punto(punto):
match punto:
case (0, 0):
return "origen"
case (x, 0):
return f"en el eje X en {x}"
case (0, y):
return f"en el eje Y en {y}"
case (x, y):
return f"punto en ({x}, {y})"
print(describir_punto((0, 0))) # origen
print(describir_punto((5, 0))) # en el eje X en 5
print(describir_punto((3, 4))) # punto en (3, 4)
Pattern matching de mappings (dicts)
def procesar_evento(evento: dict) -> str:
match evento:
case {"tipo": "click", "x": x, "y": y}:
return f"Click en ({x}, {y})"
case {"tipo": "teclado", "tecla": tecla, "modificador": "ctrl"}:
return f"Ctrl+{tecla.upper()}"
case {"tipo": "teclado", "tecla": tecla}:
return f"Tecla: {tecla}"
case {"tipo": tipo}:
return f"Evento desconocido: {tipo}"
case _:
return "Formato de evento inválido"
print(procesar_evento({"tipo": "click", "x": 100, "y": 200}))
print(procesar_evento({"tipo": "teclado", "tecla": "s", "modificador": "ctrl"}))
print(procesar_evento({"tipo": "teclado", "tecla": "enter"}))
Pattern matching de clases
from dataclasses import dataclass
@dataclass
class Circulo:
radio: float
@dataclass
class Rectangulo:
ancho: float
alto: float
@dataclass
class Triangulo:
base: float
altura: float
def calcular_area(figura) -> float:
import math
match figura:
case Circulo(radio=r):
return math.pi * r ** 2
case Rectangulo(ancho=w, alto=h):
return w * h
case Triangulo(base=b, altura=h):
return 0.5 * b * h
case _:
raise ValueError(f"Figura no reconocida: {type(figura)}")
print(f"{calcular_area(Circulo(5)):.2f}") # 78.54
print(f"{calcular_area(Rectangulo(4, 6)):.2f}") # 24.00
print(f"{calcular_area(Triangulo(3, 8)):.2f}") # 12.00
Guards con if
def clasificar_numero(n: int) -> str:
match n:
case 0:
return "cero"
case n if n < 0:
return f"negativo: {n}"
case n if n % 2 == 0:
return f"par positivo: {n}"
case n:
return f"impar positivo: {n}"
print(clasificar_numero(0)) # cero
print(clasificar_numero(-5)) # negativo: -5
print(clasificar_numero(4)) # par positivo: 4
print(clasificar_numero(7)) # impar positivo: 7
Ejemplo real: parsear respuestas JSON de una API
def manejar_respuesta(respuesta: dict) -> str:
match respuesta:
case {"status": 200, "data": {"usuarios": usuarios}} if len(usuarios) > 0:
return f"Encontrados {len(usuarios)} usuarios"
case {"status": 200, "data": {"usuarios": []}}:
return "Lista de usuarios vacía"
case {"status": 404, "mensaje": msg}:
return f"No encontrado: {msg}"
case {"status": codigo} if 500 <= codigo < 600:
return f"Error de servidor: {codigo}"
case {"status": codigo}:
return f"Respuesta inesperada: {codigo}"
print(manejar_respuesta({"status": 200, "data": {"usuarios": ["Ana", "Luis"]}}))
print(manejar_respuesta({"status": 404, "mensaje": "Usuario no existe"}))
print(manejar_respuesta({"status": 503}))
Capturar con as
def procesar(dato):
match dato:
case [x, y] as punto if x > 0 and y > 0:
print(f"Punto en el primer cuadrante: {punto}")
case [x, y]:
print(f"Punto fuera del primer cuadrante: ({x}, {y})")
case str() as s if s.startswith("http"):
print(f"URL detectada: {s}")
case _:
print("Dato no reconocido")
procesar([3, 4]) # primer cuadrante
procesar([-1, 2]) # fuera del primer cuadrante
procesar("https://python.org") # URL detectada
El match no reemplaza a todos los if: brilla cuando la lógica depende de la estructura de los datos (tipo, forma, campos presentes). Para condiciones simples de un solo valor, el if sigue siendo más claro. Pero cuando procesas eventos, resultados de API, comandos CLI o mensajes entre componentes, el match hace el código significativamente más legible.
