Deje de escribir cadenas de espagueti if-else: análisis de JSON con el caso de coincidencia de Python

Si trabaja en ciencia de datos, ingeniería de datos o como desarrollador frontend/backend, trabaja con JSON. Para los profesionales, básicamente lo único que es inevitable es la muerte, los impuestos y el análisis de JSON. El problema es que analizar JSON suele ser una verdadera molestia.

Ya sea que esté extrayendo datos de una API REST, analizando registros o leyendo archivos de configuración, eventualmente terminará con un diccionario anidado que necesita desentrañar. Y seamos honestos: el código que escribimos para manejar estos diccionarios es a menudo…feo, por decir lo menos.

Todos hemos escrito el “Spaghetti Parser”. Ya sabes cuál. Comienza con una declaración if simple, pero luego debe verificar si existe una clave. Luego debes verificar si la lista dentro de esa clave está vacía. Entonces necesitas manejar un estado de error.

Antes de que te des cuenta, tienes una torre de 40 líneas de declaraciones if-elif-else que es difícil de leer y aún más difícil de mantener. Los oleoductos terminarán rompiéndose debido a algún caso límite imprevisto. ¡Malas vibraciones por todos lados!

En Python 3.10 que salió hace unos años, se introdujo una característica que muchos científicos de datos aún no han adoptado: Coincidencia de patrones estructurales con coincidencia y caso. A menudo se confunde con una simple declaración de “cambio” (como en C o Java), pero es mucho más poderosa. Le permite verificar la forma y estructura de sus datos, en lugar de solo su valor.

En este artículo, veremos cómo reemplazar las frágiles comprobaciones del diccionario con patrones elegantes y legibles mediante el uso de coincidencias y mayúsculas y minúsculas. Me centraré en un caso de uso específico con el que muchos de nosotros estamos familiarizados, en lugar de intentar ofrecer una descripción general de cómo se puede trabajar con coincidencias y casos.

El escenario: la respuesta API “misteriosa”

Imaginemos un escenario típico. Estás sondeando una API externa sobre la que no tienes control total. Digamos, para concretar la configuración, que la API devuelve el estado de un trabajo de procesamiento de datos en formato JSON. La API es un poco inconsistente (como suele ser el caso).

Podría devolver una respuesta de éxito:

{ “estado”: 200, “datos”: { “job_id”: 101, “resultado”: [“file_a.csv”, “file_b.csv”]
} }

O una respuesta de error:

{ “estado”: 500, “error”: “Tiempo de espera”, “retry_after”: 30 }

O tal vez una respuesta heredada extraña que sea solo una lista de ID (porque la documentación de la API le mintió):

[101, 102, 103]

A la antigua usanza: la pirámide de la perdición si no

Si estuviera escribiendo esto usando el flujo de control estándar de Python, probablemente terminaría con una codificación defensiva similar a esta:

def Process_response(respuesta): # Escenario 1: Respuesta del diccionario estándar if isinstance(respuesta, dict): status = respuesta.get(“estado”) if status == 200: # Tenemos que tener cuidado de que los ‘datos’ realmente existan data = respuesta.get(“datos”, {}) resultados = datos.get(“resultado”, []) print(f”¡Éxito! Archivos {len(resultados)} procesados.”) return resultados elif status == 500: error_msg = respuesta.get(“error”, “Error desconocido”) print(f”Error con error: {error_msg}”) return Ninguno más: print(“Código de estado desconocido recibido.”) return Ninguno # Escenario 2: Respuesta de la lista heredada elif isinstance(respuesta, lista): print(f”Lista heredada recibida with {len(response)} jobs.”) return respuesta # Escenario 3: Datos basura else: print(“Formato de respuesta no válido.”) return Ninguno

¿Por qué el código anterior me duele el alma?

Combina “Qué” con “Cómo”: está mezclando lógica empresarial (“Éxito significa estado 200”) con herramientas de verificación de tipos como isinstance() y .get(). Es detallado: dedicamos la mitad del código simplemente a verificar que existen claves para evitar un KeyError. Difícil de escanear: para comprender qué constituye un “éxito”, debe analizar mentalmente varios niveles de sangría anidados.

Una mejor manera: coincidencia de patrones estructurales

Ingrese las palabras clave de coincidencia y mayúsculas y minúsculas.

En lugar de hacer preguntas como “¿Es esto un diccionario? ¿Tiene una clave llamada estado? ¿Es esa clave 200?”, podemos simplemente describir la forma de los datos que queremos manejar. Python intenta ajustar los datos a esa forma.

Aquí está exactamente la misma lógica reescrita con coincidencia y caso:

def process_response_modern(response): respuesta de coincidencia: # Caso 1: Éxito (Coincide con claves Y valores específicos) case {“status”: 200, “data”: {“result”: results}}: print(f”¡Éxito! Procesado {len(results)} archivos.”) devuelve resultados # Caso 2: Error (Captura el mensaje de error y el tiempo de reintento) case {“status”: 500, “error”: msg, “retry_after”: time}: print(f”Error: {msg}. Reintentando en {time}s…”) return Ninguno # Caso 3: Lista heredada (coincide con cualquier lista de números enteros) case [first, *rest]: print(f”Lista heredada recibida que comienza con ID: {primero}”) return respuesta # Caso 4: General (El equivalente ‘otro’) case _: print(“Formato de respuesta no válido.”) return Ninguno

Tenga en cuenta que es unas cuantas líneas más cortas, pero ésta no es la única ventaja.

Por qué la combinación de patrones estructurales es asombrosa

Se me ocurren al menos tres razones por las que la coincidencia de patrones estructurales con coincidencia y caso mejora la situación anterior.

1. Desembalaje de variables implícitas

Observe lo que sucedió en el Caso 1:

caso {“estado”: 200, “datos”: {“resultado”: resultados}}:

No sólo buscamos las llaves. Simultáneamente verificamos que el estado sea 200 Y extrajimos el valor del resultado en una variable llamada resultados.

Reemplazamos data = respuesta.get(“data”).get(“result”) con una ubicación de variable simple. Si la estructura no coincide (por ejemplo, falta el resultado), este caso simplemente se omite. Sin KeyError, sin fallas.

2. Patrón “Comodines”

En el Caso 2, utilizamos mensaje y hora como marcadores de posición:

caso {“estado”: 500, “error”: mensaje, “retry_after”: hora}:

Esto le dice a Python: espero un diccionario con estado 500 y algún valor correspondiente a las claves “error” y “retry_after”. Cualesquiera que sean esos valores, vincúlelos a las variables msg y time para poder usarlos inmediatamente.

3. Desestructuración de listas

En el Caso 3, manejamos la respuesta de lista:

caso [first, *rest]:

Este patrón coincide con cualquier lista que tenga al menos un elemento. Vincula el primer elemento al primero y el resto de la lista al resto. Esto es increíblemente útil para algoritmos recursivos o para procesar colas.

Agregar “guardias” para control adicional

A veces, igualar la estructura no es suficiente. Desea hacer coincidir una estructura solo si se cumple una condición específica. Puede hacer esto agregando una cláusula if directamente al caso.

Imaginemos que solo queremos procesar la lista heredada si contiene menos de 10 elementos.

caso [first, *rest] if len(rest) < 9: print(f"Procesando lote pequeño que comienza con {first}")

Si la lista es demasiado larga, este caso fracasa y el código pasa al siguiente caso (o al caso general _).

Conclusión

No estoy sugiriendo que reemplace cada declaración if simple con un bloque de coincidencia. Sin embargo, debería considerar seriamente el uso de coincidencias y mayúsculas y minúsculas cuando:

Análisis de respuestas de API: como se muestra arriba, este es el caso de uso ideal. Manejo de datos polimórficos: cuando una función puede recibir un int, un str o un dict y necesita comportarse de manera diferente para cada uno. Atravesar árboles AST o JSON: si está escribiendo scripts para extraer o limpiar datos web desordenados.

Como profesionales de datos, nuestro trabajo suele consistir en un 80 % en limpiar datos y en un 20 % en modelar. Cualquier cosa que haga que la fase de limpieza sea menos propensa a errores y más legible es una gran ganancia para la productividad.

Considere deshacerse de los espaguetis si no. Deje que las herramientas de fósforo y estuche hagan el trabajo pesado.

Si está interesado en IA, ciencia de datos o ingeniería de datos, sígueme o conéctate en LinkedIn.