Sentencia match de Python
Aprende la coincidencia de patrones estructurales en Python con match/case: literales, secuencias, mapeos, patrones de clase, guardas y el comodín — con ejemplos.
Python 3.10 introdujo la coincidencia de patrones estructurales mediante la sentencia match — una forma potente de ramificar según la forma y el contenido de los datos, no solo por igualdad. Este capítulo cubre todo, desde la sintaxis básica de match/case hasta patrones avanzados como la desestructuración de secuencias, patrones de mapeo, patrones de clase, guardas y casos de uso reales.
Antes de leer este capítulo deberías estar familiarizado con if/else en Python, funciones de Python y las estructuras de datos básicas (listas, tuplas, diccionarios).
¿Qué es la coincidencia de patrones estructurales?
La coincidencia de patrones estructurales te permite inspeccionar la estructura de un objeto — su tipo, los valores de sus campos, la forma de una secuencia — y ejecutar código diferente según qué patrón encaje. Va mucho más allá de una simple comprobación if x == y.
Considera el enrutamiento de un código de estado HTTP. Con cadenas if/elif se escribe:
if status == 200:
print("OK")
elif status == 404:
print("Not Found")
elif status == 500:
print("Internal Server Error")
else:
print("Unknown status")Con match la intención es más clara:
match status:
case 200:
print("OK")
case 404:
print("Not Found")
case 500:
print("Internal Server Error")
case _:
print("Unknown status")La verdadera ventaja aparece cuando el sujeto es un objeto complejo — una tupla, un diccionario o un dataclass — y quieres desestructurarlo mientras haces la coincidencia.
Sintaxis básica
match subject:
case pattern1:
# runs if subject matches pattern1
case pattern2:
# runs if subject matches pattern2
case _:
# wildcard — runs if nothing else matchedReglas a recordar:
matchycaseson palabras clave suaves — solo son palabras clave en este contexto y aún pueden usarse como nombres de variables en otras partes de tu código.- Cada bloque
casese prueba en orden; gana la primera coincidencia y las demás se omiten. - El bloque
case _:es el comodín — siempre coincide y actúa como valor predeterminado general. - Se requiere Python 3.10+. Ejecutar esto en Python 3.9 o anterior genera un
SyntaxError.
Patrones literales
El patrón más simple coincide con un valor concreto: un número, una string, True, False o 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 statusPatrones OR (|)
Usa | dentro de un case para coincidir con cualquiera de varios literales:
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")) # TrueLos patrones OR también funcionan con números, None y otros tipos literales.
Patrones de captura
Un patrón de captura es un nombre simple (no un literal de string, no un nombre con puntos) que coincide con cualquier cosa y vincula el valor coincidente a ese nombre para su uso en el cuerpo:
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 en el ejemplo anterior es un patrón de captura — vincula el valor coincidente a la variable local other. Esto se parece mucho al comodín _, pero _ descarta el valor mientras que una captura con nombre lo conserva.
Un nombre simple en un case es siempre una captura, nunca una comparación. Si quieres comparar contra una constante definida en otro lugar, usa un nombre con puntos como Status.OK o envuélvelo en una guarda (case x if x == my_constant:).
Patrones de secuencia
Un patrón de secuencia coincide con listas, tuplas o cualquier secuencia, y puede desestructurar los elementos en variables al mismo tiempo.
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)Usar * para capturar el resto
Un *nombre dentro de un patrón de secuencia recopila los elementos restantes, igual que el desempaquetado de iterables:
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)Usa [first, *_] si solo quieres capturar el primer elemento y descartar el resto.
Patrones de mapeo
Un patrón de mapeo coincide con diccionarios (o cualquier Mapping). Solo especificas las claves que te interesan — las claves adicionales en el sujeto se ignoran.
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 eventPunto clave: un patrón de mapeo nunca falla por tener claves adicionales en el sujeto. {"type": "click", "button": button} coincide incluso si el evento también contiene coordenadas "x" e "y".
Para capturar los pares clave/valor restantes, usa **rest:
match event:
case {"type": "click", **rest}:
print(f"Click event with extra data: {rest}")Patrones de clase
Un patrón de clase coincide con una instancia de una clase específica y extrae sus atributos. Esto es especialmente útil con dataclasses porque sus atributos se exponen por nombre automáticamente.
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 5Observa el patrón de clase anidado en el caso Circle: Point(x=cx, y=cy) se hace coincidir dentro del patrón Circle. Los patrones pueden componerse con una profundidad arbitraria.
Para tipos incorporados como int, str, float y bool puedes usar patrones posicionales con un ú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 typeLa palabra clave as (el patrón AS) vincula el valor coincidente completo a un nombre, incluso después de una verificación de tipo.
Guardas
Una guarda es una condición if añadida después de un patrón. El case solo coincide cuando el patrón encaja y la guarda evalúa a 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 oddLas guardas se evalúan después de que el patrón estructural coincida, por lo que las variables capturadas están disponibles dentro de ellas. Una guarda fallida no impide que se prueben los casos posteriores.
El patrón comodín _
_ es el comodín universal. Coincide con cualquier valor y no vincula nada (el valor se descarta). Debería ser el último case en un bloque match. Sin él, un match que no encuentra ningún case coincidente simplemente no hace nada — no se genera ningún error.
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'_ también puede aparecer dentro de un patrón 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)")Combinar patrones: un ejemplo del mundo real
Los patrones anteriores se pueden combinar. Aquí hay un analizador de comandos para una aventura de texto que combina patrones de secuencia, guardas y el comodín:
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 enteredLeyendo esto de arriba abajo puedes entender inmediatamente cada comando soportado — algo que requeriría muchas más líneas de lógica if/elif para lograr la misma claridad.
match vs. if/elif — cuándo usar cada uno
| Escenario | Mejor opción |
|---|---|
| Igualdad simple con unas pocas constantes | Cualquiera; match es ligeramente más limpio |
| Coincidencia según estructura / forma de los datos | match — mucho más limpio |
| Desestructurar valores mientras se hace la coincidencia | match — no se puede hacer con if |
| Lógica con condiciones calculadas únicamente | if/elif |
| Python 3.9 o anterior | if/elif (sin match disponible) |
| Expresar una tabla de decisiones con claridad | match |
match no es un reemplazo para cada cadena if. Cuando todas las ramas comprueban condiciones booleanas calculadas (p. ej., if x > 10 and y < 5) una cadena if/elif es lo natural. match brilla cuando la condición trata sobre la forma de los datos.
Errores comunes
Los nombres de constantes no se comparan por valor
Un nombre simple en un case es siempre una captura, nunca una búsqueda:
STATUS_OK = 200
match response_code:
case STATUS_OK: # WRONG — this captures into STATUS_OK, not compares!
print("Success")Para comparar contra una constante con nombre, usa un nombre con puntos (http.HTTPStatus.OK) o una guarda:
match response_code:
case x if x == STATUS_OK:
print("Success")match no es exhaustivo por defecto
A diferencia de switch en otros lenguajes, un match que no tiene ningún case coincidente simplemente no hace nada de forma silenciosa. Añade un case _: si necesitas un manejador garantizado.
match requiere Python 3.10+
Ejecutar un bloque match en Python 3.9 o anterior genera SyntaxError: invalid syntax. Comprueba tu versión con python3 --version. Consulta la guía de inicio de Python si necesitas configurar un entorno de Python moderno.
Los patrones no son expresiones booleanas
No puedes escribir case x > 5: — eso es una guarda, no un patrón. La parte estructural (case x) debe ir primero, seguida de un if guard_expression opcional.
Resumen
| Tipo de patrón | Ejemplo de sintaxis | Qué coincide |
|---|---|---|
| Literal | case 42: | Valor exacto |
| OR | case "yes" | "y": | Cualquiera de las alternativas |
| Comodín | case _: | Cualquier cosa (descarta el valor) |
| Captura | case x: | Cualquier cosa, vincula a x |
| Secuencia | case [a, b, *rest]: | Una secuencia con al menos 2 elementos |
| Mapeo | case {"key": val}: | Un diccionario que contiene las claves dadas |
| Clase | case Point(x=0, y=y): | Una instancia con atributos coincidentes |
| AS | case int() as n: | Coincide y vincula el valor completo |
| Guarda | case x if x > 0: | Patrón + condición booleana adicional |
Práctica
Ahora que sabes cómo ramificar según la estructura de los datos, explora los bucles for de Python para iterar sobre secuencias, o los enums de Python para definir el tipo de constantes tipadas que funcionan bien con los patrones de clase.