Python

Python

Tutorial Python: Pattern matching

Aprende pattern matching en Python 3.10 con match-case para manejar datos complejos y estructuras con guards y captura de variables.

Aprende Python y certifícate

Sintaxis match-case

El pattern matching es una característica moderna de Python introducida en la versión 3.10 que permite examinar una variable y ejecutar diferentes bloques de código según su estructura y valor. La sintaxis match-case proporciona una alternativa más potente y expresiva a las tradicionales estructuras if-elif-else para ciertos escenarios.

La estructura básica de una expresión match-case sigue este patrón:

match expresion:
    case patron1:
        # Código si expresion coincide con patron1
    case patron2:
        # Código si expresion coincide con patron2
    case _:
        # Código por defecto (similar a else)

Casos simples

La forma más básica de usar match-case es para comparar valores literales:

def analizar_estado(codigo):
    match codigo:
        case 200:
            return "OK"
        case 404:
            return "No encontrado"
        case 500:
            return "Error del servidor"
        case _:
            return f"Código desconocido: {codigo}"

print(analizar_estado(200))  # OK
print(analizar_estado(418))  # Código desconocido: 418

Patrones OR

Puedes combinar varios patrones usando el operador | (OR):

def clasificar_numero(n):
    match n:
        case 0:
            return "Cero"
        case 1 | 3 | 5 | 7 | 9:
            return "Impar de un dígito"
        case 2 | 4 | 6 | 8:
            return "Par de un dígito"
        case _:
            return "Número de múltiples dígitos"

print(clasificar_numero(5))  # Impar de un dígito
print(clasificar_numero(8))  # Par de un dígito
print(clasificar_numero(42)) # Número de múltiples dígitos

Captura de valores

Puedes capturar valores en variables dentro de los patrones:

def procesar_comando(comando):
    match comando.split():
        case ["salir"]:
            return "Saliendo del programa"
        case ["guardar", nombre_archivo]:
            return f"Guardando en {nombre_archivo}"
        case ["abrir", nombre_archivo]:
            return f"Abriendo {nombre_archivo}"
        case ["ayuda"]:
            return "Comandos disponibles: salir, guardar, abrir, ayuda"
        case _:
            return "Comando no reconocido"

print(procesar_comando("guardar documento.txt"))  # Guardando en documento.txt
print(procesar_comando("ayuda"))                  # Comandos disponibles: salir, guardar, abrir, ayuda

En este ejemplo, nombre_archivo captura cualquier valor en la segunda posición de la lista.

Patrones con constantes

Para usar constantes en patrones, debes prefijarlas con _:

MAX_USUARIOS = 100
MIN_USUARIOS = 10

def validar_usuarios(cantidad):
    match cantidad:
        case _MAX_USUARIOS:
            return "Límite máximo alcanzado"
        case _MIN_USUARIOS:
            return "Límite mínimo alcanzado"
        case _:
            return "Cantidad dentro de los límites"

print(validar_usuarios(100))  # Límite máximo alcanzado
print(validar_usuarios(50))   # Cantidad dentro de los límites

Patrones con clases

El match-case es particularmente útil con objetos y clases:

class Punto:
    def __init__(self, x, y):
        self.x = x
        self.y = y

def clasificar_punto(punto):
    match punto:
        case Punto(x=0, y=0):
            return "Origen"
        case Punto(x=0, y=y):
            return f"En el eje Y, coordenada {y}"
        case Punto(x=x, y=0):
            return f"En el eje X, coordenada {x}"
        case Punto(x=x, y=y) if x == y:
            return f"En la diagonal, coordenada {x}"
        case Punto():
            return "Punto en otra posición"
        case _:
            return "No es un punto"

print(clasificar_punto(Punto(0, 0)))    # Origen
print(clasificar_punto(Punto(5, 0)))    # En el eje X, coordenada 5
print(clasificar_punto(Punto(3, 3)))    # En la diagonal, coordenada 3

Patrones con secuencias

El match-case es muy potente para trabajar con secuencias como listas y tuplas:

def analizar_coordenadas(coords):
    match coords:
        case []:
            return "Lista vacía"
        case [x, y]:
            return f"Punto 2D: ({x}, {y})"
        case [x, y, z]:
            return f"Punto 3D: ({x}, {y}, {z})"
        case [x, y, *resto]:
            return f"Punto multidimensional: empieza con ({x}, {y}) y continúa con {resto}"
        case _:
            return "No es una lista de coordenadas"

print(analizar_coordenadas([]))             # Lista vacía
print(analizar_coordenadas([10, 20]))       # Punto 2D: (10, 20)
print(analizar_coordenadas([1, 2, 3, 4]))   # Punto multidimensional: empieza con (1, 2) y continúa con [3, 4]

El operador * captura cualquier número de elementos restantes en una lista.

Patrones anidados

Puedes crear patrones anidados para estructuras de datos complejas:

def procesar_datos(datos):
    match datos:
        case {"tipo": "usuario", "id": id_usuario, "detalles": {"nombre": nombre}}:
            return f"Usuario {nombre} con ID {id_usuario}"
        case {"tipo": "producto", "id": id_producto, "precio": precio}:
            return f"Producto con ID {id_producto}, precio: {precio}"
        case {"error": mensaje, "codigo": codigo}:
            return f"Error {codigo}: {mensaje}"
        case _:
            return "Formato de datos desconocido"

print(procesar_datos({"tipo": "usuario", "id": 123, "detalles": {"nombre": "Ana"}}))
# Usuario Ana con ID 123

print(procesar_datos({"error": "Acceso denegado", "codigo": 403}))
# Error 403: Acceso denegado

Ventajas sobre if-elif-else

La sintaxis match-case ofrece varias ventajas sobre las estructuras tradicionales:

  • Legibilidad: El código es más claro y expresivo, especialmente para estructuras de datos complejas.
  • Concisión: Reduce la cantidad de código necesario para manejar múltiples casos.
  • Captura de valores: Permite extraer y nombrar partes de los datos de forma elegante.
  • Patrones estructurales: Facilita la coincidencia basada en la estructura de los datos.
# Con if-elif-else
def analizar_punto_tradicional(punto):
    if not isinstance(punto, tuple) or len(punto) != 2:
        return "No es un punto 2D"
    x, y = punto
    if x == 0 and y == 0:
        return "Origen"
    elif x == 0:
        return f"En el eje Y, coordenada {y}"
    elif y == 0:
        return f"En el eje X, coordenada {x}"
    elif x == y:
        return f"En la diagonal, coordenada {x}"
    else:
        return "Punto en otra posición"

# Con match-case
def analizar_punto_moderno(punto):
    match punto:
        case (0, 0):
            return "Origen"
        case (0, y):
            return f"En el eje Y, coordenada {y}"
        case (x, 0):
            return f"En el eje X, coordenada {x}"
        case (x, y) if x == y:
            return f"En la diagonal, coordenada {x}"
        case (_, _):
            return "Punto en otra posición"
        case _:
            return "No es un punto 2D"

Consideraciones de rendimiento

El match-case está optimizado para ser eficiente en la mayoría de los casos. Python compila los patrones en código optimizado, similar a una serie de comprobaciones condicionales. Sin embargo, para casos muy simples, un if-elif-else tradicional puede ser ligeramente más rápido.

# Ejemplo de benchmark simple
import timeit

def test_if_else(valor):
    if valor == 1:
        return "uno"
    elif valor == 2:
        return "dos"
    else:
        return "otro"

def test_match_case(valor):
    match valor:
        case 1:
            return "uno"
        case 2:
            return "dos"
        case _:
            return "otro"

# Para casos simples, la diferencia de rendimiento es mínima
tiempo_if = timeit.timeit(lambda: test_if_else(3), number=1000000)
tiempo_match = timeit.timeit(lambda: test_match_case(3), number=1000000)

La sintaxis match-case brinda una forma más expresiva y potente de manejar múltiples condiciones en Python, especialmente cuando se trabaja con estructuras de datos complejas o cuando se necesita extraer componentes de los datos durante el proceso de coincidencia.

Patrones estructurales

Los patrones estructurales son una de las características más potentes del pattern matching en Python. Estos patrones permiten examinar y descomponer estructuras de datos complejas como diccionarios, listas, tuplas y objetos de clases personalizadas en una sola operación.

A diferencia de las comparaciones simples de valores, los patrones estructurales analizan la forma y composición interna de los datos. Esto nos permite escribir código más declarativo que se centra en "qué estamos buscando" en lugar de "cómo lo estamos buscando".

Patrones para diccionarios

Los diccionarios son estructuras de datos fundamentales en Python, y el pattern matching ofrece una sintaxis elegante para trabajar con ellos:

def procesar_configuracion(config):
    match config:
        case {"debug": True, "entorno": entorno}:
            return f"Modo depuración activado en entorno {entorno}"
        case {"usuarios": usuarios, "max_conexiones": max_con} if len(usuarios) > max_con:
            return f"Advertencia: {len(usuarios)} usuarios exceden el límite de {max_con} conexiones"
        case {"version": version, "api_key": _}:
            return f"Configuración válida para API v{version}"
        case {"error": mensaje, **resto}:
            return f"Error en configuración: {mensaje}. Detalles adicionales: {resto}"
        case {}:
            return "Configuración vacía"
        case _:
            return "Formato de configuración no reconocido"

# Ejemplos de uso
print(procesar_configuracion({"debug": True, "entorno": "desarrollo"}))
# Modo depuración activado en entorno desarrollo

print(procesar_configuracion({"usuarios": ["ana", "juan", "elena"], "max_conexiones": 2}))
# Advertencia: 3 usuarios exceden el límite de 2 conexiones

Características importantes:

  • Podemos verificar la presencia de claves específicas en el diccionario
  • Podemos capturar valores asociados a claves específicas
  • El operador **resto captura todas las claves restantes no especificadas
  • Podemos combinar patrones con guardas para condiciones adicionales

Patrones para secuencias (listas y tuplas)

El pattern matching brinda herramientas poderosas para trabajar con secuencias:

def analizar_serie_temporal(datos):
    match datos:
        case []:
            return "Serie vacía"
        case [único_valor]:
            return f"Serie con un solo valor: {único_valor}"
        case [primer, *_, último] if primer == último:
            return f"Serie circular: comienza y termina con {primer}"
        case [inicio, segundo, *resto]:
            diferencia = segundo - inicio
            return f"Serie que comienza con {inicio}, {segundo} (diferencia: {diferencia})"
        case (año, mes, día):
            return f"Fecha en formato tupla: {día}/{mes}/{año}"
        case _:
            return "Formato no reconocido"

# Ejemplos de uso
print(analizar_serie_temporal([10, 15, 20, 25, 10]))
# Serie circular: comienza y termina con 10

print(analizar_serie_temporal([5, 8, 11, 14, 17]))
# Serie que comienza con 5, 8 (diferencia: 3)

print(analizar_serie_temporal((2023, 11, 15)))
# Fecha en formato tupla: 15/11/2023

Características importantes:

  • El operador *_ captura cualquier número de elementos que no necesitamos nombrar
  • Podemos combinar captura de elementos al inicio y final de la secuencia
  • El pattern matching distingue automáticamente entre listas y tuplas
  • Podemos aplicar condiciones adicionales a los valores capturados

Patrones para clases y objetos personalizados

Una de las aplicaciones más potentes del pattern matching es con objetos de clases personalizadas:

class Evento:
    def __init__(self, tipo, datos=None):
        self.tipo = tipo
        self.datos = datos or {}

class Respuesta:
    def __init__(self, codigo, contenido=None):
        self.codigo = codigo
        self.contenido = contenido

def procesar_mensaje(mensaje):
    match mensaje:
        case Evento(tipo="click", datos={"x": x, "y": y}):
            return f"Click en coordenadas ({x}, {y})"
        case Evento(tipo="login", datos={"usuario": usuario}):
            return f"Inicio de sesión de {usuario}"
        case Evento(tipo="error", datos=datos) if "crítico" in datos:
            return f"¡ERROR CRÍTICO! {datos['mensaje']}"
        case Respuesta(codigo=200, contenido=contenido):
            return f"Respuesta exitosa: {contenido}"
        case Respuesta(codigo=codigo) if 400 <= codigo < 500:
            return f"Error del cliente: código {codigo}"
        case Respuesta(codigo=codigo) if 500 <= codigo < 600:
            return f"Error del servidor: código {codigo}"
        case _:
            return "Mensaje no reconocido"

# Ejemplos de uso
print(procesar_mensaje(Evento("click", {"x": 100, "y": 200})))
# Click en coordenadas (100, 200)

print(procesar_mensaje(Respuesta(404, "Página no encontrada")))
# Error del cliente: código 404

print(procesar_mensaje(Evento("error", {"crítico": True, "mensaje": "Base de datos no disponible"})))
# ¡ERROR CRÍTICO! Base de datos no disponible

Características importantes:

  • Podemos hacer coincidencia por atributos de objetos
  • Podemos combinar patrones para atributos que son estructuras de datos complejas
  • Funciona con cualquier clase que tenga los atributos especificados
  • Podemos usar guardas para condiciones adicionales sobre los atributos

Patrones anidados y complejos

El verdadero poder de los patrones estructurales se revela cuando combinamos diferentes tipos de patrones:

def analizar_datos_iot(mensaje):
    match mensaje:
        case {"dispositivo": "sensor", "lecturas": [{"temp": t, "humedad": h} as lectura, *_]} if t > 30:
            return f"Alerta: temperatura alta ({t}°C) en lectura: {lectura}"
        case {"dispositivo": "gateway", "conexiones": conexiones} if len(conexiones) == 0:
            return "Gateway sin dispositivos conectados"
        case {"dispositivo": "actuador", "estado": {"encendido": True, "modo": modo}}:
            return f"Actuador funcionando en modo {modo}"
        case {"error": {"codigo": codigo, "detalles": detalles}}:
            return f"Error {codigo}: {detalles}"
        case _:
            return "Formato de mensaje IoT no reconocido"

# Ejemplo de uso
datos = {
    "dispositivo": "sensor",
    "id": "temp-01",
    "lecturas": [
        {"temp": 32.5, "humedad": 60, "timestamp": "2023-11-15T14:30:00"},
        {"temp": 31.8, "humedad": 62, "timestamp": "2023-11-15T14:35:00"}
    ]
}

print(analizar_datos_iot(datos))
# Alerta: temperatura alta (32.5°C) en lectura: {'temp': 32.5, 'humedad': 60, 'timestamp': '2023-11-15T14:30:00'}

En este ejemplo, utilizamos:

  • Patrones anidados para examinar estructuras dentro de estructuras
  • La palabra clave as para capturar un subpatrón completo
  • Guardas condicionales basadas en valores capturados
  • Combinación de patrones de diccionario y lista

Patrones con clases de datos (dataclasses)

Las dataclasses de Python funcionan especialmente bien con pattern matching:

from dataclasses import dataclass
from typing import List, Optional

@dataclass
class Usuario:
    nombre: str
    rol: str
    permisos: List[str]

@dataclass
class Recurso:
    tipo: str
    propietario: Optional[Usuario] = None
    publico: bool = False

def verificar_acceso(usuario, recurso, accion):
    match (usuario, recurso, accion):
        case (Usuario(rol="admin", permisos=_), _, _):
            return True
        case (Usuario(permisos=permisos), Recurso(propietario=propietario), _) if usuario == propietario:
            return True
        case (Usuario(permisos=permisos), Recurso(publico=True), "leer"):
            return True
        case (Usuario(permisos=permisos), Recurso(tipo=tipo), accion) if f"{accion}_{tipo}" in permisos:
            return True
        case _:
            return False

# Ejemplos de uso
admin = Usuario("Ana", "admin", ["eliminar_usuarios"])
usuario = Usuario("Carlos", "usuario", ["leer_documento", "editar_imagen"])
documento = Recurso("documento", usuario, False)
imagen_publica = Recurso("imagen", None, True)

print(verificar_acceso(admin, documento, "eliminar"))  # True (admin)
print(verificar_acceso(usuario, documento, "leer"))    # True (propietario)
print(verificar_acceso(usuario, imagen_publica, "leer"))  # True (recurso público)

Las dataclasses proporcionan una estructura clara que se integra perfectamente con los patrones estructurales, haciendo que el código sea más legible y mantenible.

Consideraciones de diseño

Al trabajar con patrones estructurales, es importante tener en cuenta:

  • Orden de los casos: Python evalúa los casos en orden secuencial, por lo que debes colocar los patrones más específicos antes que los más generales.
  • Exhaustividad: Considera siempre incluir un caso por defecto (case _:) para manejar entradas inesperadas.
  • Complejidad: Si un patrón se vuelve demasiado complejo, considera dividirlo en múltiples funciones o usar guardas para claridad.
  • Rendimiento: Para estructuras de datos muy grandes, el pattern matching puede ser menos eficiente que accesos directos, pero la claridad del código suele compensar esta diferencia.

Los patrones estructurales transforman la forma en que trabajamos con datos complejos en Python, permitiéndonos escribir código más declarativo, expresivo y fácil de entender.

Guards y captura de variables

El pattern matching en Python se vuelve aún más potente cuando combinamos la captura de variables con condiciones adicionales llamadas guards. Estos mecanismos nos permiten no solo identificar patrones estructurales, sino también establecer restricciones sobre los valores capturados y reutilizarlos de manera eficiente.

Captura de variables

La captura de variables es el proceso mediante el cual asignamos nombres a partes de la estructura que estamos analizando. Esto nos permite acceder a esos valores dentro del bloque de código correspondiente:

def analizar_punto(punto):
    match punto:
        case (x, y):
            return f"Coordenadas capturadas: x={x}, y={y}"

print(analizar_punto((10, 20)))  # Coordenadas capturadas: x=10, y=20

En este ejemplo, x y y son variables de captura que reciben los valores de la tupla. A diferencia de un desempaquetado normal, la captura en pattern matching ocurre solo si el patrón coincide completamente.

Captura con nombres específicos

Podemos capturar valores con nombres específicos en estructuras complejas:

def procesar_transaccion(transaccion):
    match transaccion:
        case {"monto": monto, "divisa": divisa, "fecha": fecha}:
            return f"Transacción de {monto} {divisa} realizada el {fecha}"

print(procesar_transaccion({"monto": 100, "divisa": "EUR", "fecha": "2023-11-15", "id": "T123"}))
# Transacción de 100 EUR realizada el 2023-11-15

Aquí capturamos solo los campos que nos interesan, ignorando otros campos como id.

Captura con el operador AS

El operador as nos permite capturar subpatrones completos mientras seguimos analizando su estructura interna:

def analizar_medicion(datos):
    match datos:
        case {"sensor": id_sensor, "lecturas": [primera, *resto] as todas_lecturas}:
            return f"Sensor {id_sensor}: {len(todas_lecturas)} lecturas, primera: {primera}"

datos = {"sensor": "temp-01", "lecturas": [32.5, 31.8, 30.2]}
print(analizar_medicion(datos))
# Sensor temp-01: 3 lecturas, primera: 32.5

En este ejemplo, capturamos tanto la primera lectura individualmente como la lista completa de lecturas usando as todas_lecturas.

Guards (condiciones de guarda)

Los guards son condiciones adicionales que se evalúan después de que un patrón coincide. Se introducen con la palabra clave if y permiten un control más preciso sobre cuándo debe ejecutarse un caso:

def clasificar_temperatura(temp):
    match temp:
        case float() | int() as t if t < 0:
            return f"Temperatura bajo cero: {t}°C"
        case float() | int() as t if 0 <= t <= 20:
            return f"Temperatura fresca: {t}°C"
        case float() | int() as t if 20 < t <= 30:
            return f"Temperatura agradable: {t}°C"
        case float() | int() as t:
            return f"Temperatura calurosa: {t}°C"
        case _:
            return "Valor no es una temperatura válida"

print(clasificar_temperatura(-5))    # Temperatura bajo cero: -5°C
print(clasificar_temperatura(25))    # Temperatura agradable: 25°C
print(clasificar_temperatura(35))    # Temperatura calurosa: 35°C
print(clasificar_temperatura("35"))  # Valor no es una temperatura válida

En este ejemplo:

  1. Primero verificamos si el valor coincide con el tipo (float o int)
  2. Luego capturamos ese valor en la variable t
  3. Finalmente aplicamos una condición adicional con if

Guards con múltiples variables

Los guards pueden utilizar múltiples variables capturadas en expresiones complejas:

def analizar_rango(rango):
    match rango:
        case (inicio, fin) if inicio < fin:
            return f"Rango válido de {inicio} a {fin}"
        case (inicio, fin) if inicio == fin:
            return f"Rango degenerado: {inicio}"
        case (inicio, fin):
            return f"Rango invertido: {fin} a {inicio}"

print(analizar_rango((10, 20)))  # Rango válido de 10 a 20
print(analizar_rango((5, 5)))    # Rango degenerado: 5
print(analizar_rango((30, 20)))  # Rango invertido: 20 a 30

Combinando guards con patrones complejos

La verdadera potencia de los guards se revela cuando los combinamos con patrones estructurales complejos:

def validar_usuario(datos):
    match datos:
        case {"nombre": nombre, "edad": edad, "roles": roles} if edad < 18 and "admin" in roles:
            return f"Error: {nombre} es menor de edad y no puede ser administrador"
        case {"nombre": nombre, "edad": edad} if edad < 18:
            return f"{nombre} es menor de edad, acceso limitado"
        case {"nombre": nombre, "roles": roles} if "admin" in roles:
            return f"{nombre} tiene privilegios de administrador"
        case {"nombre": nombre, "email": email} if "@" in email:
            return f"Usuario {nombre} con email válido"
        case {"nombre": nombre}:
            return f"Usuario {nombre} con datos incompletos"
        case _:
            return "Datos de usuario inválidos"

print(validar_usuario({"nombre": "Ana", "edad": 16, "roles": ["usuario", "admin"]}))
# Error: Ana es menor de edad y no puede ser administrador

print(validar_usuario({"nombre": "Carlos", "edad": 25, "roles": ["admin"]}))
# Carlos tiene privilegios de administrador

Captura de variables en patrones anidados

Podemos capturar variables en estructuras profundamente anidadas:

def analizar_pedido(pedido):
    match pedido:
        case {"cliente": {"nombre": nombre, "premium": True}, 
              "productos": [{"id": id_producto, "precio": precio} as primer_producto, *_]} if precio > 100:
            return f"Cliente premium {nombre} con producto caro ({precio}€): {primer_producto}"
        case {"cliente": {"nombre": nombre}, "productos": productos} if sum(p.get("precio", 0) for p in productos) > 200:
            return f"Pedido grande de {nombre}: total superior a 200€"
        case {"cliente": cliente, "productos": []}:
            return f"Carrito vacío para {cliente.get('nombre', 'cliente desconocido')}"
        case _:
            return "Formato de pedido no reconocido"

pedido = {
    "cliente": {"nombre": "Laura", "premium": True, "desde": 2020},
    "productos": [
        {"id": "P123", "nombre": "Auriculares", "precio": 129.99},
        {"id": "P456", "nombre": "Funda", "precio": 19.99}
    ]
}

print(analizar_pedido(pedido))
# Cliente premium Laura con producto caro (129.99€): {'id': 'P123', 'nombre': 'Auriculares', 'precio': 129.99}

Captura con comodines

Podemos usar comodines (_) para partes del patrón que no necesitamos capturar:

def extraer_información(datos):
    match datos:
        case [_, segundo, *_, último]:
            return f"Segundo elemento: {segundo}, último: {último}"
        case {"principal": {"secundario": {"valor": valor}}, **_}:
            return f"Valor anidado encontrado: {valor}"
        case _:
            return "Patrón no reconocido"

print(extraer_información([10, 20, 30, 40, 50]))
# Segundo elemento: 20, último: 50

print(extraer_información({"principal": {"secundario": {"valor": 42}}, "extra": "ignorado"}))
# Valor anidado encontrado: 42

Captura con tipos específicos

Podemos combinar la captura de variables con verificación de tipos:

def procesar_valor(valor):
    match valor:
        case str() as texto if texto.isdigit():
            return f"Texto numérico: {texto}"
        case str() as texto:
            return f"Texto no numérico: {texto}"
        case int() as numero if numero % 2 == 0:
            return f"Número par: {numero}"
        case int() as numero:
            return f"Número impar: {numero}"
        case list() as lista if all(isinstance(x, int) for x in lista):
            return f"Lista de enteros: {lista}"
        case _:
            return "Tipo no manejado"

print(procesar_valor("123"))      # Texto numérico: 123
print(procesar_valor("abc"))      # Texto no numérico: abc
print(procesar_valor(42))         # Número par: 42
print(procesar_valor([1, 2, 3]))  # Lista de enteros: [1, 2, 3]

Captura en clases con atributos dinámicos

Podemos capturar valores de atributos en clases que los definen dinámicamente:

class Configuración:
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)

def validar_configuración(config):
    match config:
        case Configuración(debug=True, entorno=entorno) if entorno != "producción":
            return f"Modo debug activado en entorno {entorno}"
        case Configuración(timeout=timeout) if timeout < 100:
            return f"Advertencia: timeout demasiado bajo ({timeout}ms)"
        case Configuración(max_usuarios=max_usuarios, conexiones=conexiones) if conexiones > max_usuarios:
            return f"Error: {conexiones} conexiones exceden el límite de {max_usuarios} usuarios"
        case _:
            return "Configuración válida"

print(validar_configuración(Configuración(debug=True, entorno="desarrollo")))
# Modo debug activado en entorno desarrollo

print(validar_configuración(Configuración(timeout=50, host="localhost")))
# Advertencia: timeout demasiado bajo (50ms)

Buenas prácticas con guards y captura de variables

  1. Orden de los casos: Coloca los patrones más específicos antes que los más generales, ya que Python evalúa los casos en orden secuencial.
def analizar_número(n):
    match n:
        case int() as x if x == 0:  # Caso específico primero
            return "Cero"
        case int() as x if x > 0:   # Caso más general después
            return "Positivo"
        case int():                 # Caso aún más general
            return "Negativo"
        case _:
            return "No es un entero"
  1. Evita efectos secundarios en guards: Los guards deben ser expresiones puras sin efectos secundarios.
# Incorrecto (efectos secundarios en guard)
case datos if print("Evaluando...") or len(datos) > 10:
    return "Muchos datos"

# Correcto
case datos:
    print("Evaluando...")
    if len(datos) > 10:
        return "Muchos datos"
  1. Usa nombres descriptivos para las variables capturadas:
# Poco descriptivo
case {"usuario": u, "permisos": p} if "admin" in p:
    return f"{u} es administrador"

# Más descriptivo
case {"usuario": nombre_usuario, "permisos": permisos} if "admin" in permisos:
    return f"{nombre_usuario} es administrador"
  1. Combina captura con desempaquetado cuando sea apropiado:
def procesar_coordenadas(datos):
    match datos:
        case {"puntos": [*puntos]} if len(puntos) >= 3:
            x1, y1 = puntos[0]
            x2, y2 = puntos[1]
            x3, y3 = puntos[2]
            # Cálculos con las coordenadas...
            return "Triángulo procesado"

Los guards y la captura de variables transforman el pattern matching de una simple herramienta de coincidencia a un poderoso mecanismo para expresar lógica compleja de manera concisa y legible. Estas características permiten escribir código que comunica claramente la intención y reduce la posibilidad de errores lógicos.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Python online

Ejercicios de esta lección Pattern matching

Evalúa tus conocimientos de esta lección Pattern matching con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la sintaxis básica y uso del pattern matching con match-case en Python.
  • Aprender a utilizar patrones simples, OR, captura de valores y patrones con constantes.
  • Aplicar patrones estructurales para trabajar con diccionarios, secuencias y objetos personalizados.
  • Utilizar guards para añadir condiciones adicionales a los patrones y capturar variables en estructuras anidadas.
  • Conocer buenas prácticas y consideraciones de diseño para escribir código claro y eficiente con pattern matching.