Coincidencia de patrones estructurales en Python

Mehvish Ashiq 10 octubre 2023
  1. Introducción a la coincidencia de patrones estructurales y su importancia
  2. Utilice la coincidencia de patrones estructurales en Python
Coincidencia de patrones estructurales en Python

Antes de Python 3.10, no teníamos ninguna forma integrada de usar la coincidencia de patrones estructurales, lo que se conoce como cambio de caso en otros lenguajes de programación. A partir del lanzamiento de Python 3.10, no podemos usar la declaración match ... case para emular la declaración switch-case.

Este tutorial presenta la coincidencia de patrones estructurales y su importancia en Python. También utiliza diferentes patrones para demostrar el uso de la declaración match ... case.

Introducción a la coincidencia de patrones estructurales y su importancia

A partir de los primeros días de 2021, no pudimos usar la palabra clave coincidir en las versiones publicadas de Python que son inferiores o iguales a 3.9. En ese momento, estábamos acostumbrados a simular cambiar... caso usando un diccionario o sentencias if/elif/else anidadas.

Pero, Python 3.10 ha introducido una nueva función conocida como coincidencia de patrones estructurales (declaración match ... case). Es equivalente a una declaración de cambiar... caso como la que tenemos en Java, C++ y muchos otros lenguajes de programación.

Esta nueva característica nos ha permitido escribir declaraciones de control de flujo de errores simples, fáciles de leer y con una probabilidad mínima de error.

Utilice la coincidencia de patrones estructurales en Python

La coincidencia de patrones estructurales se usa como una declaración de cambio de caso y es más poderosa que esto. ¿Cómo? Exploremos algunos ejemplos a continuación para aprender sus usos en diferentes situaciones.

Uso básico de la sentencia match ... case

Código de ejemplo:

# >= Python 3.10
colour = "blue"
match colour:
    case "green":
        print("The specified colour is green")
    case "white":
        print("Wow, you've picked white")
    case "green":
        print("Great, you are going with green colour")
    case "blue":
        print("Blue like sky...")

Producción :

Blue like sky...

Aquí, primero tenemos una variable color que contiene azul. Luego, usamos la palabra clave match, que hace coincidir el valor de la variable color con varios casos específicos donde cada caso comienza con la palabra clave case seguida de un patrón que queremos comparar o verificar.

El patrón puede ser uno de los siguientes:

  • patrón literal
  • patrón de captura
  • patrón comodín
  • patrón de valor constante
  • patrón de secuencia
  • patrón de mapeo
  • patrón de clase
  • O patrón
  • patrón de morsa

La instrucción coincidir... caso solo ejecuta el código bajo el primer caso que coincidió.

¿Qué pasa si ningún caso coincide? ¿Cómo se enterará el usuario? Para eso, podemos tener un caso predeterminado de la siguiente manera.

Código de ejemplo:

# >= Python 3.10
colour = "yellow"
match colour:
    case "green":
        print("The specified colour is green")
    case "white":
        print("Wow, you've picked white")
    case "green":
        print("Great, you are going with green colour")
    case "blue":
        print("Blue like sky...")
    case other:
        print("No match found!")

Producción :

No match found!

Utilice match ... case para detectar y deconstruir estructuras de datos

Código de ejemplo:

# >= Python 3.10
student = {"name": {"first": "Mehvish", "last": "Ashiq"}, "section": "B"}

match student:
    case {"name": {"first": firstname}}:
        print(firstname)

Producción :

Mehvish

En el ejemplo anterior, la coincidencia de patrones estructurales está en acción en las siguientes dos líneas de código:

# >= Python 3.10
match student:
    case {"name": {"first": firstname}}:

Usamos la instrucción match ... case para encontrar el nombre del estudiante extrayéndolo de la estructura de datos student. Aquí, el estudiante es un diccionario que contiene la información del estudiante.

La línea caso especifica nuestro patrón para que coincida con el estudiante. Teniendo en cuenta el ejemplo anterior, buscamos un diccionario con la clave el "nombre" cuyo valor es un nuevo diccionario.

Este diccionario anidado contiene una clave "first" cuyo valor está vinculado a la variable firstname. Finalmente, usamos la variable firstname para imprimir el valor.

Hemos aprendido el patrón de mapeo aquí si lo observa más profundamente. ¿Cómo? El patrón de mapeo se parece a {"student": s, "emails": [*es]}, que coincide con el mapeo con al menos un conjunto de claves específicas.

Si todos los subpatrones coinciden con sus valores correspondientes, vincula cualquier vínculo de subpatrón durante la coincidencia con los valores correspondientes a las claves. Si queremos permitir la captura de elementos adicionales, podemos agregar **rest al final del patrón.

Utilice match ... case con el patrón de captura y el patrón de secuencia

Código de ejemplo:

# >= Python 3.10
def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [first, *rest]:
            return first + sum_list_of_numbers(rest)


sum_list_of_numbers([1, 2, 3, 4])

Producción :

10

Aquí, usamos la función recursiva para usar el patrón de captura para capturar la coincidencia con el patrón especificado y vincularlo al nombre.

En este ejemplo de código, el primer caso devuelve 0 como suma si coincide con una lista vacía. El segundo caso utiliza el patrón de secuencia con dos patrones de captura para hacer coincidir las listas con uno de varios elementos/elementos.

Aquí, el primer elemento de una lista se captura y vincula al “primer” nombre, mientras que el segundo patrón de captura, *rest, usa la sintaxis de desempaquetado para hacer coincidir cualquier número de elementos/elementos.

Tenga en cuenta que el resto se une a la lista que tiene todos los elementos/elementos de números, excluyendo el primero. Para obtener el resultado, llamamos a la función sum_list_of_numbers() pasando una lista de números como se indicó anteriormente.

Utilice match ... case con el patrón comodín

Código de ejemplo:

# >= Python 3.10
def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [first, *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            incorrect_type = numbers.__class__.__name__
            raise ValueError(
                f"Incorrect Values. We Can only Add lists of numbers,not {incorrect_type!r}"
            )


sum_list_of_numbers({"1": "2", "3": "4"})

Producción :

ValueError: Incorrect Values. We Can only Add lists of numbers, not 'dict'

Hemos aprendido el concepto de usar el patrón comodín mientras aprendimos el uso básico de la instrucción coincidir ... caso, pero no introdujimos el término patrón comodín allí. Imagine un escenario en el que los dos primeros casos no coinciden y necesitamos tener un patrón general como nuestro caso final.

Por ejemplo, queremos generar un error si obtenemos cualquier otro tipo de estructura de datos en lugar de una lista. Aquí, podemos usar _ como un patrón comodín, que coincidirá con cualquier cosa sin vincularse al nombre. Agregamos manejo de errores en este caso final para informar al usuario.

¿Qué dices? ¿Es bueno ir con nuestro patrón? Vamos a probarlo llamando a la función sum_list_of_numbers() pasando una lista de valores de cadena de la siguiente manera:

sum_list_of_numbers(["1", "2", "3", "4"])

Producirá el siguiente error:

TypeError: can only concatenate str (not "int") to str

Entonces, podemos decir que el patrón aún no es lo suficientemente infalible. ¿Por qué? Porque pasamos la estructura de datos de tipo lista a la función sum_list_of_numbers() pero tenemos valores de tipo cadena, no de tipo int como esperábamos.

Consulte la siguiente sección para saber cómo resolverlo.

Usa match ... case con el patrón de clase

Código de ejemplo:

# >= Python 3.10
def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [int(first), *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            raise ValueError(f"Incorrect values! We can only add lists of numbers")


sum_list_of_numbers(["1", "2", "3", "4"])

Producción :

ValueError: Incorrect values! We can only add lists of numbers

El caso base (el primer caso) devuelve 0; por lo tanto, sumar solo funciona para los tipos que podemos sumar con números. Tenga en cuenta que Python no sabe cómo agregar cadenas de texto y números.

Entonces, podemos usar el patrón de clase para restringir nuestro patrón para que solo coincida con números enteros. El patrón de clase es similar al patrón de mapeo pero coincide con los atributos en lugar de las claves.

Usa match ... case con el patrón OR

Código de ejemplo:

# >= Python 3.10
def sum_list_of_numbers(numbers):
    match numbers:
        case []:
            return 0
        case [int(first) | float(first), *rest]:
            return first + sum_list_of_numbers(rest)
        case _:
            raise ValueError(f"Incorrect values! We can only add lists of numbers")

Supongamos que queremos hacer que la función sum_list_of_numbers() funcione para una lista de valores, ya sean valores de tipo int o de tipo flotante. Usamos el patrón OR representado con un signo de tubería (|).

El código anterior debe generar el ValueError si la lista especificada contiene valores que no sean valores de tipo int o float. Probemos considerando los tres escenarios a continuación.

Prueba 1: pase una lista que tenga valores de tipo int:

sum_list_of_numbers([1, 2, 3, 4])  # output is 10

Prueba 2: Pase una lista que tenga valores de tipo flotante:

sum_list_of_numbers([1.0, 2.0, 3.0, 4.0])  # output is 10.0

Prueba 3: pase una lista que tenga cualquier otro tipo, excepto los tipos int y float:

sum_list_of_numbers(["1", "2", "3", "4"])
# output is ValueError: Incorrect values! We can only add lists of numbers

Como puede ver, la función sum_list_of_numbers() funciona tanto para valores de tipo int como float debido al uso del patrón OR.

Usa match ... case con el Patrón Literal

Código de ejemplo:

# >= Python 3.10
def say_hello(name):
    match name:
        case "Mehvish":
            print(f"Hi, {name}!")
        case _:
            print("Howdy, stranger!")


say_hello("Mehvish")

Producción :

Hi, Mehvish!

Este ejemplo usa un patrón literal que coincide con el objeto literal, por ejemplo, un número explícito o una cadena, como ya hicimos mientras aprendíamos el uso básico de la instrucción match ... case.

Es el tipo de patrón más básico y nos permite simular una instrucción switch ... case similar a Java, C++ y otros lenguajes de programación. Puede visitar esta página para conocer todos los patrones.

Mehvish Ashiq avatar Mehvish Ashiq avatar

Mehvish Ashiq is a former Java Programmer and a Data Science enthusiast who leverages her expertise to help others to learn and grow by creating interesting, useful, and reader-friendly content in Computer Programming, Data Science, and Technology.

LinkedIn GitHub Facebook