Manejo Profesional de Errores con Python – Parte 1

Como desarrolladores sabemos que en cada proyecto nuevo o en los que ya tenemos construidos, se nos van a presentar errores durante la escritura de código. Hay errores grandes que generan otros errores y hay errores pequeños o aislados, pero que al fin y al cabo no permiten que nuestros proyectos funcionen adecuadamente. En Python existen maneras profesionales de gestionar los errores, si bien cada desarrollador tiene su propia manera de manejarlos, en este Post te compartiré ciertas técnicas para el Manejo Profesional de Errores con Python, vamos con ello.

Partes

Antes de continuar te invito a leer los siguiente artículos:

Asimismo te invito a escuchar el Podcast“Las Buenas Prácticas Un Hábito Importante en la Programación” “¿ Qué Es NoCode Development ?” (Anchor Podcast):  

Spotify: Sound Cloud: Apple Podcasts Anchor Podcasts

Bien ahora continuemos con el Post: Manejo Profesional de Errores con Python – Parte 1.

Si tu proyecto no tiene una estrategia coherente para el manejo de errores, no será confiable, la experiencia del usuario será deficiente y tendrás muchos desafíos para depurar y solucionar problemas.

La clave del éxito es ser consciente de todos estos aspectos entrelazados, considerarlos explícitamente y formar una solución que aborde cada punto.

Códigos de Estado Frente a Excepciones

Hay dos modelos principales de manejo de errores: códigos de estado y excepciones. Los códigos de estado pueden ser utilizados por cualquier lenguaje de programación. Las excepciones requieren soporte de idioma/tiempo de ejecución.

Python admite excepciones. Python y su librería estándar usan excepciones generosamente para informar sobre muchas situaciones excepcionales como errores de IO, división por cero, indexación fuera de los límites y también algunas situaciones no tan excepcionales como el final de la iteración (aunque está oculto). La mayoría de las librerías hacen lo mismo y plantean excepciones.

Eso significa que tu código tendrá que manejar las excepciones generadas por Python y las bibliotecas de todos modos, por lo que también puede generar excepciones de tu código cuando sea necesario y no confiar en los códigos de estado.

Ejemplo Rápido

def dividir():
    return 4 / 0
def miFuncion1():
    raise Exception("No nos llames. Te llamaremos")
def miFuncion2():
    try:
        dividir()
    except Exception as e:
        print(e)
     
    try:
        miFuncion1()
    except Exception as e:
        print(e)

Aquí está la salida al llamar a la función miFuncion2()la cual ejecuta en su interior las funciones dividir() y miFuncion1()

miFuncion2()
division by zero 
No nos llames. Te llamaremos

Excepciones de Python

Las excepciones de Python son objetos organizados en una jerarquía de clases. A continuación toda la jerarquía:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StandardError
      |    +-- BufferError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |         +-- UnicodeError
      |              +-- UnicodeDecodeError
      |              +-- UnicodeEncodeError
      |              +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
  +-- ImportWarning
  +-- UnicodeWarning
  +-- BytesWarning

Hay varias excepciones especiales que se derivan directamente de BaseException, como SystemExit, KeyboardInterrupt y GeneratorExit. Luego está la clase Exception, que es la clase base para StopIteration, StandardError y Warning. Todos los errores estándar se derivan de StandardError.

Cuando genera una excepción o alguna función que llamaste genera una excepción, ese flujo de código normal termina y la excepción comienza a propagarse por la pila de llamadas hasta que encuentra un controlador de excepciones adecuado. Si no hay un controlador de excepciones disponible para manejarlo, el proceso (o más exactamente, el subproceso actual) terminará con un mensaje de excepción no manejado.

Generar Excepciones

Generar excepciones es muy fácil. Simplemente usa la palabra clave raise para generar un objeto que es una subclase de la clase Exception. Podría ser una instancia de Exception así mismo, una de las excepciones estándar (p. ej RuntimeError.), o una subclase Exception que tu mismo derives. Aquí hay un pequeño fragmento de código que demuestra todos los casos:

# Generar una instancia de la propia clase Exception 
raise Exception('Ummm... algo anda mal')
 
# Generar una instancia de la clase RuntimeError 
raise RuntimeError('Ummm... algo anda mal')
 
# Genera una subclase personalizada de Exception que mantiene la marca de tiempo en que se creó la excepción 
from datetime import datetime
 
class SuperError(Exception):
    def __init__(self, message):
        Exception.__init__(message)
        self.when = datetime.now()
 
raise SuperError('Ummm... algo anda mal')

Captura de Excepciones

Atrapa excepciones con la cláusula except, veamos el siguiente ejemplo:

while True:
    try:
        x = int(input("Porfavor ingresa un número: "))
        break
    except ValueError:
        print("¡Ups! Ese no es un número válido. Intentar otra vez...")

Cuando ejecuta el bloque de código anterior, el programa primero ejecuta el código después de la cláusula try. Si no ocurre ninguna excepción, el programa salta la cláusula except. Por otro lado, si ocurre un error, el programa ejecuta la declaración después de la cláusula except.

Si ingresas un número entero, el programa funciona como se esperaba. Sin embargo, si ingresas un flotante o un string, el programa deja de ejecutarse. 

Porfavor ingresa un número: 20.5
¡Ups! Ese no es un número válido. Intentar otra vez...
Porfavor ingresa un número: gelatina
¡Ups! Ese no es un número válido. Intentar otra vez...
Porfavor ingresa un número: 36.0
¡Ups! Ese no es un número válido. Intentar otra vez...
Porfavor ingresa un número:

Cuando detecta una excepción, tienes tres opciones:

  • Manejar la Excepción.
  • Generar la Misma Excepción para permitir que los niveles superiores lo manejen.
  • Levantar una Excepción Diferente en lugar de la original.

Manejar la Excepción

Por ejemplo, si recibes un archivo de entrada que puede estar en diferentes formatos (JSON, YAML), puedes intentar analizarlo con diferentes analizadores. Si el analizador JSON generó una excepción de que el archivo no es un archivo JSON válido, lo acepta e intenta con el analizador YAML. Si el analizador YAML también falla, deja que la excepción se propague.

import json
import yaml
 
def parsear_archivo(nombredelarchivo):
    try:
        return json.load(open(nombredelarchivo))
    except json.JSONDecodeError
        return yaml.load(open(nombredelarchivo))

Ten en cuenta que otras excepciones (p. ej., archivo no encontrado o sin permisos de lectura) se propagarán y no serán detectadas por la cláusula de excepción específica. Esta es una buena política en este caso en el que deseas probar el análisis YAML solo si el análisis JSON falló debido a un problema de codificación JSON.

Si desea manejar todas las excepciones, simplemente usa except Exception. Por ejemplo:

def imprimir_tipo_excepcion(func, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        print(type(e))

Ten en cuenta que al agregar as e, vincula el objeto de excepción al nombre e disponible en tu cláusula de excepción.

Generar la Misma Excepción

Para generar la excepción nuevamente, simplemente agrega raise sin argumentos dentro de tu controlador. Esto te permite realizar un manejo local, pero también permite que los niveles superiores lo manejen.

Aquí, la función invocar_funcion() imprime el tipo de excepción en la consola y luego vuelve a generar la excepción.

def invocar_funcion(func, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        print(type(e))
        raise

Levantar una Excepción Diferente

Hay varios casos en los que desearías generar una excepción diferente. A veces, desesa agrupar varias excepciones diferentes de bajo nivel en una sola categoría que el código de nivel superior maneja de manera uniforme. En casos de pedido, debes transformar la excepción al nivel de usuario y proporcionar algún contexto específico de la aplicación.

Hasta aqui llegamos con esta primera parte sobre el Manejo Profesional de Errores con Python.

Ten Paciencia, lo que quiero es que conozcas bien estos métodos y no llenarte el capitulo de mucho contenido porque te puedes marear y no tendrás un óptimo aprendizaje. 

Nota (s)

  • En la siguiente parte y última, veremos otros métodos para el Manejo Profesional de Errores con Python.
  • No olvides que debemos usar la Tecnología para hacer cosas Buenas por el Mundo. 

Síguenos en nuestras Redes Sociales para que no te pierdas nuestros próximos contenidos.