Ir al contenido principal

Excepciones múltiples y jerarquía

Introducción

Cuando trabajamos con código Python, es común encontrarnos con diferentes tipos de errores que pueden interrumpir la ejecución de nuestro programa. En el artículo anterior, aprendimos cómo capturar estas excepciones usando bloques try-except. Sin embargo, en aplicaciones reales, a menudo necesitamos gestionar múltiples tipos de excepciones de forma diferente. En este artículo, exploraremos cómo manejar excepciones múltiples y entenderemos la jerarquía de excepciones en Python, lo que nos permitirá crear código más robusto y con mejor manejo de errores.

Capturando múltiples excepciones

Excepciones múltiples en bloques separados

La forma más básica de manejar varios tipos de excepciones es utilizando múltiples bloques except:

try:
    numero = int(input("Introduce un número: "))
    resultado = 100 / numero
    print(f"El resultado es: {resultado}")
except ValueError:
    print("Error: Debes introducir un número válido.")
except ZeroDivisionError:
    print("Error: No se puede dividir entre cero.")

En este ejemplo:

  • Si el usuario introduce algo que no es un número, se lanzará un ValueError y se ejecutará el primer bloque except.
  • Si el usuario introduce un cero, se lanzará un ZeroDivisionError y se ejecutará el segundo bloque except.

Agrupando excepciones

También podemos agrupar múltiples excepciones en un solo bloque except cuando queremos manejarlas de la misma manera:

try:
    numero = int(input("Introduce un número: "))
    resultado = 100 / numero
    print(f"El resultado es: {resultado}")
except (ValueError, ZeroDivisionError):
    print("Error: Debes introducir un número válido que no sea cero.")

Capturando la información de la excepción

Es posible acceder a la información de la excepción utilizando la cláusula as:

try:
    archivo = open("archivo_inexistente.txt", "r")
    contenido = archivo.read()
    archivo.close()
except FileNotFoundError as error:
    print(f"Error al abrir el archivo: {error}")
    print(f"Tipo de error: {type(error)}")

La variable error contiene toda la información de la excepción, lo que nos permite acceder a sus detalles y mostrar mensajes más informativos.

Jerarquía de excepciones

Las excepciones en Python están organizadas en una jerarquía de clases. Todas las excepciones heredan de la clase base BaseException, aunque la mayoría de las excepciones que utilizaremos heredan de Exception.

Estructura básica de la jerarquía

BaseException
 ├── SystemExit
 ├── KeyboardInterrupt
 ├── GeneratorExit
 └── Exception
      ├── StopIteration
      ├── ArithmeticError
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ImportError
      │    └── ModuleNotFoundError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError
      ├── NameError
      │    └── UnboundLocalError
      ├── OSError
      │    ├── BlockingIOError
      │    ├── ChildProcessError
      │    ├── ConnectionError
      │    │    ├── BrokenPipeError
      │    │    ├── ConnectionAbortedError
      │    │    ├── ConnectionRefusedError
      │    │    └── ConnectionResetError
      │    ├── FileExistsError
      │    ├── FileNotFoundError
      │    ├── InterruptedError
      │    ├── IsADirectoryError
      │    ├── NotADirectoryError
      │    ├── PermissionError
      │    ├── ProcessLookupError
      │    └── TimeoutError
      ├── ReferenceError
      ├── RuntimeError
      │    ├── NotImplementedError
      │    └── RecursionError
      ├── SyntaxError
      │    └── IndentationError
      │         └── TabError
      ├── SystemError
      ├── TypeError
      ├── ValueError
      │    └── UnicodeError
      │         ├── UnicodeDecodeError
      │         ├── UnicodeEncodeError
      │         └── UnicodeTranslateError
      └── Warning

Implicaciones de la jerarquía

La jerarquía de excepciones es importante porque cuando capturamos una excepción de un tipo determinado, también capturaremos todas las excepciones que heredan de ese tipo. Por ejemplo:

try:
    # Código que puede generar diferentes tipos de LookupError
    lista = [1, 2, 3]
    print(lista[10])  # IndexError (subclase de LookupError)
    
    diccionario = {"clave": "valor"}
    print(diccionario["otra_clave"])  # KeyError (subclase de LookupError)
except LookupError as error:
    print(f"Error de búsqueda: {error}")

En este código, capturamos tanto IndexError como KeyError porque ambos son subclases de LookupError.

El orden importa

Es importante tener en cuenta el orden de los bloques except cuando manejamos excepciones en diferentes niveles de la jerarquía:

try:
    # Código que puede lanzar excepciones
    resultado = 10 / 0
except Exception as e:
    print(f"Se ha capturado una excepción: {e}")
except ZeroDivisionError as e:
    print(f"División por cero: {e}")

En este ejemplo, el bloque except ZeroDivisionError nunca se ejecutará porque Exception es una clase base de ZeroDivisionError, y las excepciones se manejan en el orden en que aparecen los bloques except. Siempre debemos colocar las excepciones más específicas antes que las más generales:

try:
    # Código que puede lanzar excepciones
    resultado = 10 / 0
except ZeroDivisionError as e:
    print(f"División por cero: {e}")
except Exception as e:
    print(f"Se ha capturado una excepción: {e}")

Ejemplo práctico de uso de la jerarquía

Un uso común de la jerarquía de excepciones es proporcionar un manejo específico para ciertos tipos de errores, con un manejo más genérico para otros:

def leer_configuracion(nombre_archivo):
    try:
        with open(nombre_archivo, 'r') as archivo:
            lineas = archivo.readlines()
            # Procesar configuración
            return lineas
    except FileNotFoundError:
        print(f"El archivo {nombre_archivo} no existe. Usando configuración por defecto.")
        return []
    except PermissionError:
        print(f"No tienes permisos para leer {nombre_archivo}.")
        return None
    except OSError as e:
        print(f"Error del sistema al acceder al archivo: {e}")
        return None
    except Exception as e:
        print(f"Error inesperado: {e}")
        return None

# Probamos con diferentes escenarios
configuracion = leer_configuracion("config.txt")

En este ejemplo:

  1. Manejamos específicamente FileNotFoundError y PermissionError.
  2. Luego manejamos cualquier otro OSError (que es la clase base de ambos errores anteriores, pero capturará otros errores de sistema operativo).
  3. Finalmente, capturamos cualquier otra excepción con Exception.

Resumen

En este artículo, hemos aprendido cómo manejar múltiples excepciones en Python y hemos explorado la jerarquía de excepciones. Ahora sabemos cómo capturar diferentes tipos de errores, agruparlos cuando sea necesario y acceder a la información detallada de cada excepción. También hemos comprendido la importancia del orden al capturar excepciones de diferentes niveles en la jerarquía. Estos conocimientos nos permitirán crear código más robusto que pueda manejar situaciones de error de manera elegante y específica. En el próximo artículo, exploraremos las cláusulas else y finally, que complementan el manejo de excepciones en Python.