Manejo de excepciones y registro de eventos (logging)

PythonPythonBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderás cómo implementar el manejo de excepciones en Python. Comprenderás cómo utilizar el módulo logging (registro) para un mejor informe de errores, lo cual es crucial para depurar y mantener tu código.

También practicarás modificando el archivo reader.py para manejar los errores de manera elegante en lugar de que el programa se bloquee. Esta experiencia práctica mejorará tu capacidad para escribir programas robustos en Python.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/ModulesandPackagesGroup(["Modules and Packages"]) python(("Python")) -.-> python/ErrorandExceptionHandlingGroup(["Error and Exception Handling"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/ControlFlowGroup -.-> python/for_loops("For Loops") python/ModulesandPackagesGroup -.-> python/standard_libraries("Common Standard Libraries") python/ErrorandExceptionHandlingGroup -.-> python/catching_exceptions("Catching Exceptions") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("Raising Exceptions") subgraph Lab Skills python/conditional_statements -.-> lab-132507{{"Manejo de excepciones y registro de eventos (logging)"}} python/for_loops -.-> lab-132507{{"Manejo de excepciones y registro de eventos (logging)"}} python/standard_libraries -.-> lab-132507{{"Manejo de excepciones y registro de eventos (logging)"}} python/catching_exceptions -.-> lab-132507{{"Manejo de excepciones y registro de eventos (logging)"}} python/raising_exceptions -.-> lab-132507{{"Manejo de excepciones y registro de eventos (logging)"}} end

Comprender las excepciones en Python

En este paso, vamos a aprender sobre las excepciones en Python. Las excepciones son un concepto importante en la programación. Nos ayudan a manejar situaciones inesperadas que pueden ocurrir mientras un programa está en ejecución. También descubriremos por qué el código actual se bloquea cuando intenta procesar datos no válidos. Comprender esto te ayudará a escribir programas de Python más robustos y confiables.

¿Qué son las excepciones?

En Python, las excepciones son eventos que ocurren durante la ejecución de un programa y interrumpen el flujo normal de las instrucciones. Piénsalo como un obstáculo en una carretera. Cuando todo va bien, tu programa sigue un camino establecido, al igual que un automóvil en una carretera despejada. Pero cuando ocurre un error, Python crea un objeto de excepción. Este objeto es como un informe que contiene información sobre lo que salió mal, como el tipo de error y dónde ocurrió en el código.

Si estas excepciones no se manejan adecuadamente, hacen que el programa se bloquee. Cuando se produce un bloqueo, Python muestra un mensaje de rastreo (traceback). Este mensaje es como un mapa que te muestra la ubicación exacta en el código donde ocurrió el error. Es muy útil para la depuración.

Examinar el código actual

Primero, echemos un vistazo a la estructura del archivo reader.py. Este archivo contiene funciones que se utilizan para leer y convertir datos CSV. Para abrir el archivo en el editor, necesitamos navegar al directorio correcto. Usaremos el comando cd en la terminal.

cd /home/labex/project

Ahora que estamos en el directorio correcto, echemos un vistazo al contenido de reader.py. Este archivo tiene varias funciones importantes:

  1. convert_csv(): Esta función toma filas de datos y utiliza una función de conversión proporcionada para convertirlas. Es como una máquina que toma materias primas (filas de datos) y las transforma en una forma diferente según una receta específica (la función de conversión).
  2. csv_as_dicts(): Esta función lee datos CSV y los convierte en una lista de diccionarios. También realiza la conversión de tipos, lo que significa que se asegura de que cada pieza de datos en el diccionario sea del tipo correcto, como una cadena, un entero o un número de punto flotante.
  3. read_csv_as_dicts(): Esta es una función envolvente (wrapper). Es como un gerente que llama a la función csv_as_dicts() para que se haga el trabajo.

Demostrar el problema

Veamos qué sucede cuando el código intenta procesar datos no válidos. Abriremos un intérprete de Python, que es como un campo de juego donde podemos probar nuestro código de Python de forma interactiva. Para abrir el intérprete de Python, usaremos el siguiente comando en la terminal:

python3

Una vez que el intérprete de Python esté abierto, intentaremos leer el archivo missing.csv. Este archivo contiene algunos datos faltantes o no válidos. Usaremos la función read_csv_as_dicts() del archivo reader.py para leer los datos.

from reader import read_csv_as_dicts
port = read_csv_as_dicts('missing.csv', types=[str, int, float])

Cuando ejecutes este código, deberías ver un mensaje de error como este:

Traceback (most recent call last):
  ...
ValueError: invalid literal for int() with base 10: ''

Este error ocurre porque el código intenta convertir una cadena vacía en un entero. Una cadena vacía no representa un entero válido, por lo que Python no puede realizar la conversión. La función se bloquea en el primer error que encuentra y deja de procesar el resto de los datos válidos en el archivo.

Para salir del intérprete de Python, escribe el siguiente comando:

exit()

Comprender el flujo de errores

El error ocurre en la función convert_csv(), específicamente en la siguiente línea:

return list(map(lambda row: converter(headers, row), rows))

La función map() aplica la función converter a cada fila en la lista rows. La función converter intenta aplicar los tipos (str, int, float) a cada fila. Pero cuando encuentra una fila con datos faltantes, falla. La función map() no tiene una forma incorporada de manejar excepciones. Entonces, cuando se produce una excepción, todo el proceso se bloquea.

En el siguiente paso, modificarás el código para manejar estas excepciones de manera elegante. Esto significa que en lugar de bloquearse, el programa podrá manejar los errores y continuar procesando el resto de los datos.

Implementar el manejo de excepciones

En este paso, nos centraremos en hacer que tu código sea más robusto. Cuando un programa encuentra datos incorrectos, a menudo se bloquea. Pero podemos utilizar una técnica llamada manejo de excepciones para manejar estos problemas de manera elegante. Modificarás el archivo reader.py para implementar esto. El manejo de excepciones permite que tu programa siga ejecutándose incluso cuando se enfrenta a datos inesperados, en lugar de detenerse abruptamente.

Comprender los bloques try-except

Python proporciona una forma poderosa de manejar excepciones utilizando bloques try-except. Analicemos cómo funcionan.

try:
    ## Code that might cause an exception
    result = risky_operation()
except SomeExceptionType as e:
    ## Code that runs if the exception occurs
    handle_exception(e)

En el bloque try, colocas el código que podría generar una excepción. Una excepción es un error que ocurre durante la ejecución de un programa. Por ejemplo, si intentas dividir un número entre cero, Python generará una excepción ZeroDivisionError. Cuando se produce una excepción en el bloque try, Python detiene la ejecución del código en el bloque try y salta al bloque except correspondiente. El bloque except contiene el código que manejará la excepción. SomeExceptionType es el tipo de excepción que quieres capturar. Puedes capturar tipos específicos de excepciones o utilizar una excepción general Exception para capturar todos los tipos de excepciones. La parte as e te permite acceder al objeto de excepción, que contiene información sobre el error.

Modificar el código

Ahora, apliquemos lo que hemos aprendido sobre los bloques try-except a la función convert_csv(). Abre el archivo reader.py en tu editor.

  1. Reemplaza la función convert_csv() actual con el siguiente código:
def convert_csv(rows, converter, header=True):
    """
    Convert a sequence of rows to an output sequence according to a conversion function.
    """
    if header:
        headers = next(rows)
    else:
        headers = []

    result = []
    for row_idx, row in enumerate(rows, start=1):
        try:
            ## Try to convert the row
            result.append(converter(headers, row))
        except Exception as e:
            ## Print a warning message for bad rows
            print(f"Row {row_idx}: Bad row: {row}")
            continue

    return result

En esta nueva implementación:

  • Utilizamos un bucle for en lugar de map() para procesar cada fila. Esto nos da más control sobre el procesamiento de cada fila.
  • Encerramos el código de conversión en un bloque try-except. Esto significa que si se produce una excepción durante la conversión de una fila, el programa no se bloqueará. En lugar de eso, saltará al bloque except.
  • En el bloque except, imprimimos un mensaje de error para las filas no válidas. Esto nos ayuda a identificar qué filas tienen problemas.
  • Después de imprimir el mensaje de error, utilizamos la declaración continue para omitir la fila actual y continuar procesando las filas restantes.

Guarda el archivo después de realizar estos cambios.

Probar tus cambios

Probemos tu código modificado con el archivo missing.csv. Primero, abre el intérprete de Python ejecutando el siguiente comando en tu terminal:

python3

Una vez que estés en el intérprete de Python, ejecuta el siguiente código:

from reader import read_csv_as_dicts
port = read_csv_as_dicts('missing.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(port)}")

Cuando ejecutes este código, deberías ver mensajes de error para cada fila problemática. Pero el programa seguirá procesando y devolverá las filas válidas. Aquí tienes un ejemplo de lo que podrías ver:

Row 4: Bad row: ['C', '', '53.08']
Row 7: Bad row: ['DIS', '50', 'N/A']
Row 8: Bad row: ['GE', '', '37.23']
Row 13: Bad row: ['INTC', '', '21.84']
Row 17: Bad row: ['MCD', '', '51.11']
Row 19: Bad row: ['MO', '', '70.09']
Row 22: Bad row: ['PFE', '', '26.40']
Row 26: Bad row: ['VZ', '', '42.92']
Number of valid rows processed: 20

Verifiquemos también que el programa funcione correctamente con datos válidos. Ejecuta el siguiente código en el intérprete de Python:

valid_port = read_csv_as_dicts('valid.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(valid_port)}")

Deberías ver que todas las filas se procesan sin errores. Aquí tienes un ejemplo de la salida:

Number of valid rows processed: 17

Para salir del intérprete de Python, ejecuta el siguiente comando:

exit()

Ahora tu código es más robusto. Puede manejar datos no válidos de manera elegante omitiendo las filas incorrectas en lugar de bloquearse. Esto hace que tu programa sea más confiable y amigable para el usuario.

✨ Revisar Solución y Practicar

Implementar el registro de eventos (logging)

En este paso, vamos a mejorar tu código. En lugar de usar simples mensajes de impresión (print), utilizaremos el módulo logging de Python para un adecuado registro de eventos. El registro de eventos es una excelente manera de llevar un seguimiento de lo que está haciendo tu programa, especialmente cuando se trata de manejar errores y entender el flujo de tu código.

Comprender el módulo de registro de eventos

El módulo logging en Python nos proporciona una forma flexible de enviar mensajes de registro desde nuestras aplicaciones. Es mucho más poderoso que simplemente usar declaraciones de impresión simples. Esto es lo que puede hacer:

  1. Diferentes niveles de registro (DEBUG, INFO, WARNING, ERROR, CRITICAL): Estos niveles nos ayudan a categorizar la importancia de los mensajes. Por ejemplo, DEBUG es para información detallada que es útil durante el desarrollo, mientras que CRITICAL es para errores graves que pueden detener el programa.
  2. Formato de salida configurable: Podemos decidir cómo se verán los mensajes de registro, como agregar marcas de tiempo u otra información útil.
  3. Los mensajes se pueden dirigir a diferentes salidas (consola, archivos, etc.): Podemos elegir mostrar los mensajes de registro en la consola, guardarlos en un archivo o incluso enviarlos a un servidor remoto.
  4. Filtrado de registros basado en la gravedad: Podemos controlar qué mensajes vemos en función de su nivel de registro.

Agregar registro de eventos a reader.py

Ahora, cambiemos tu código para usar el módulo de registro de eventos. Abre el archivo reader.py.

Primero, necesitamos importar el módulo logging y configurar un registrador (logger) para este módulo. Agrega el siguiente código en la parte superior del archivo:

import logging

## Set up a logger for this module
logger = logging.getLogger(__name__)

La declaración import logging importa el módulo logging para que podamos usar sus funciones. logging.getLogger(__name__) crea un registrador para este módulo específico. Usar __name__ asegura que el registrador tenga un nombre único relacionado con el módulo.

A continuación, modificaremos la función convert_csv() para usar el registro de eventos en lugar de declaraciones de impresión. Aquí está el código actualizado:

def convert_csv(rows, converter, header=True):
    """
    Convert a sequence of rows to an output sequence according to a conversion function.
    """
    if header:
        headers = next(rows)
    else:
        headers = []

    result = []
    for row_idx, row in enumerate(rows, start=1):
        try:
            ## Try to convert the row
            result.append(converter(headers, row))
        except Exception as e:
            ## Log a warning message for bad rows
            logger.warning(f"Row {row_idx}: Bad row: {row}")
            ## Log the reason at debug level
            logger.debug(f"Row {row_idx}: Reason: {str(e)}")
            continue

    return result

Los cambios principales aquí son:

  • Reemplazamos print() con logger.warning() para el mensaje de error. De esta manera, el mensaje se registra con el nivel de advertencia adecuado, y podemos controlar su visibilidad más adelante.
  • Agregamos un nuevo mensaje logger.debug() con detalles sobre la excepción. Esto nos da más información sobre lo que salió mal, pero solo se muestra si el nivel de registro se establece en DEBUG o inferior.
  • str(e) convierte la excepción en una cadena, para que podamos mostrar la razón del error en el mensaje de registro.

Después de realizar estos cambios, guarda el archivo.

Probar el registro de eventos

Probemos tu código con el registro de eventos habilitado. Abre el intérprete de Python ejecutando el siguiente comando en tu terminal:

python3

Una vez que estés en el intérprete de Python, ejecuta el siguiente código:

import logging
import reader

## Configure logging level to see all messages
logging.basicConfig(level=logging.DEBUG)

port = reader.read_csv_as_dicts('missing.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(port)}")

Aquí, primero importamos el módulo logging y nuestro módulo reader. Luego, establecemos el nivel de registro en DEBUG usando logging.basicConfig(level=logging.DEBUG). Esto significa que veremos todos los mensajes de registro, incluyendo DEBUG, INFO, WARNING, ERROR y CRITICAL. Luego llamamos a la función read_csv_as_dicts del módulo reader e imprimimos el número de filas válidas procesadas.

Deberías ver una salida como esta:

WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
DEBUG:reader:Row 4: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
DEBUG:reader:Row 7: Reason: could not convert string to float: 'N/A'
...
Number of valid rows processed: 20

Observa que el módulo de registro de eventos agrega un prefijo a cada mensaje, mostrando el nivel de registro (WARNING/DEBUG) y el nombre del módulo.

Ahora, veamos qué sucede si cambiamos el nivel de registro para mostrar solo las advertencias. Ejecuta el siguiente código en el intérprete de Python:

## Reset the logging configuration
import logging
logging.basicConfig(level=logging.WARNING)

port = reader.read_csv_as_dicts('missing.csv', types=[str, int, float])

Esta vez, establecemos el nivel de registro en WARNING usando logging.basicConfig(level=logging.WARNING). Ahora solo verás los mensajes de WARNING, y los mensajes de DEBUG estarán ocultos:

WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
...

Esto muestra la ventaja de usar diferentes niveles de registro. Podemos controlar cuántos detalles se muestran en los registros sin cambiar nuestro código.

Para salir del intérprete de Python, ejecuta el siguiente comando:

exit()

¡Felicidades! Ahora has implementado un adecuado manejo de excepciones y registro de eventos en tu programa de Python. Esto hace que tu código sea más confiable y te brinda mejor información cuando se producen errores.

Resumen

En este laboratorio, has aprendido varios conceptos clave sobre el manejo de excepciones y el registro de eventos (logging) en Python. En primer lugar, has comprendido cómo se producen las excepciones durante el procesamiento de datos e implementado bloques try-except para manejarlas de manera elegante. También has modificado el código para seguir procesando los datos válidos cuando surgen errores.

En segundo lugar, has aprendido sobre el módulo de registro de eventos de Python y sus ventajas en comparación con las declaraciones de impresión (print). Has implementado diferentes niveles de registro como WARNING y DEBUG y has visto cómo configurar el registro de eventos para diferentes niveles de detalle. Estas habilidades son cruciales para escribir aplicaciones robustas de Python, especialmente cuando se trata de datos externos propensos a errores, construir aplicaciones desatendidas o desarrollar sistemas que necesitan información de diagnóstico. Ahora puedes aplicar estas técnicas a tus proyectos de Python para una mejor confiabilidad y mantenibilidad.