Skip to main content

Manejar excepcione en Python

¿Qué son las excepciones?

Definimos una excepción es un evento que ocurre cuando el flujo normal de un programa se ve interrumpido debido a un error o una situación inesperada. Por ejemplo, cuando intentamos dividir un número por cero, se lanza una excepción llamada ‘ZeroDivisionError’. Python tiene varias excepciones predefinidas, pero también podemos crear nuestras propias excepciones personalizadas. Las excepciones más frecuentes con las que nos podemos encontrar son:

  • Exception: Clase base para todas las excepciones. No debe usarse directamente, pero puede ser útil para capturar cualquier excepción no específica.
  • AttributeError: Se lanza cuando se intenta acceder a un atributo o método que no existe en un objeto.
  • ImportError: Se lanza cuando no se puede importar un módulo o paquete.
  • IndexError: Se lanza cuando se intenta acceder a un índice fuera del rango de una lista, tupla u otro objeto secuencial.
  • KeyError: Se lanza cuando se intenta acceder a un elemento inexistente en un diccionario utilizando una clave que no está presente.
  • NameError: Se lanza cuando se intenta utilizar una variable o función que no ha sido definida.
  • TypeError: Se lanza cuando se pasa un argumento de un tipo incorrecto a una función o se realiza una operación no permitida entre diferentes tipos de datos.
  • ValueError: Se lanza cuando una función recibe un argumento de tipo correcto, pero su valor es inapropiado o fuera del rango esperado.
  • ZeroDivisionError: Se lanza cuando se intenta dividir un número por cero.
  • FileNotFoundError: Se lanza cuando se intenta abrir un archivo que no existe.
  • OSError: Se lanza cuando ocurre un error relacionado con el sistema operativo. Puede ser causado por problemas de archivos, directorios, acceso denegado, etc.
  • RuntimeError: Se lanza cuando se detecta un error que no encaja en ninguna de las otras categorías. Es una excepción general para errores en tiempo de ejecución.

El manejo correcto de las excepciones resulta imprescindible para aportar solidez a un programa, ya que establece una estrategia de recuperación ante algún eventual fallo en el programa, bien sea por un uso incorrecto del usuario que está utilizando el programa, bien sea porque surge alguna eventualidad en el entorno donde se está ejecutando el programa. En la siguiente url puedes encontrar la lista completa de excepciones en Python: https://docs.python.org/3/library/exceptions.html

Estructura básica de las excepciones en Python

La estructura básica de una excepción en Python se basa en el uso de bloques try, except, else y finally. Veamos con detenimiento cada uno de esos bloques:

  • Bloque try: El bloque try contiene el código que puede generar una excepción. Si se produce una excepción dentro de este bloque, el flujo de ejecución se pasa al bloque except correspondiente.
  • Bloque except: El bloque except especifica cómo manejar una excepción. Puede haber varios bloques except para manejar diferentes tipos de excepciones. Se ejecuta el primer bloque except que coincida con el tipo de excepción generada.
  • Bloque else: El bloque else es opcional y se ejecuta si no se produce ninguna excepción dentro del bloque try. Se utiliza para ejecutar código que debe realizarse solo si no hubo errores.
  • Bloque finally: El bloque finally es opcional y se ejecuta independientemente de si se produce una excepción o no. Este bloque se utiliza para especificar acciones que deben realizarse en cualquier caso, como cerrar archivos o liberar recursos.

Veamos un ejemplo donde intervienen estos cuatro bloques:

try:
    # Código que puede generar una excepción
    resultado = 10 / 0
except ZeroDivisionError:
    # Código que se ejecuta si se produce la excepción "ZeroDivisionError"
    print("No se puede dividir por cero")
else:
    # Código que se ejecuta si no se produce ninguna excepción
    print("La división se realizó con éxito")
finally:
    # Código que se ejecuta siempre, independientemente de si se produce o no una excepción
    print("Bloque finally ejecutado")

En el ejemplo anterior intentamos dividir un número por cero, lo que generará una excepción ZeroDivisionError. El bloque except correspondiente manejará esta excepción y mostrará un mensaje de error. El bloque finally se ejecutará al final, independientemente de si se produce la excepción o no.

Es importante tener en cuenta que no es necesario implementar un bloque para cada excepción que queramos controlar, al contrario, podemos hacer que un solo bloque controle un conjunto de excepciones como podemos ver en el siguiente ejempo:

def dividir_numeros(numerador, denominador):
    try:
        resultado = numerador / denominador
    except (ZeroDivisionError, TypeError) as e:
        if isinstance(e, ZeroDivisionError):
            print("Error: No se puede dividir por cero")
        elif isinstance(e, TypeError):
            print("Error: Los argumentos deben ser números")
        return None
    else:
        print("La división se realizó con éxito")
        return resultado
    finally:
        print("Función dividir_numeros finalizada")

# Caso 1: División exitosa
print("Caso 1:")
res = dividir_numeros(10, 2)
print(f"Resultado: {res}\n")

# Caso 2: División por cero
print("Caso 2:")
res = dividir_numeros(10, 0)
print(f"Resultado: {res}\n")

# Caso 3: Argumentos no numéricos
print("Caso 3:")
res = dividir_numeros(10, "dos")
print(f"Resultado: {res}\n")

En el ejemplo anterior, se muestra cómo manejar múltiples excepciones en un solo bloque except. En este ejemplo, crearemos una función que divide dos números y maneja las excepciones ZeroDivisionError y TypeError en un único bloque except. La función dividir_numeros intenta dividir dos números. Si se produce una excepción ZeroDivisionError o TypeError dentro del bloque try, el bloque except correspondiente manejará ambas excepciones. Utilizamos la palabra clave as para asociar la excepción capturada a la variable e, y luego utilizamos isinstance para determinar el tipo de excepción y manejarla adecuadamente.

Creando excepciones personalizadas en Python

Para crear excepciones personalizadas en Python, debes definir una nueva clase que herede de la clase base Exception. Luego, puedes agregar atributos y métodos personalizados a tu clase de excepción según sea necesario. Veamos un ejemplo.

# Definición de la excepción personalizada
class ExcepcionPersonalizada(Exception):
    def __init__(self, mensaje, codigo_error):
        super().__init__(mensaje)
        self.codigo_error = codigo_error

# Función que utiliza la excepción personalizada
def dividir_numeros(numerador, denominador):
    try:
        if denominador == 0:
            raise ExcepcionPersonalizada("Error: No se puede dividir por cero", 1001)
        resultado = numerador / denominador
    except ExcepcionPersonalizada as e:
        print(f"{e}. Código de error: {e.codigo_error}")
        return None
    else:
        print("La división se realizó con éxito")
        return resultado
    finally:
        print("Función dividir_numeros finalizada")

# Caso 1: División exitosa
print("Caso 1:")
res = dividir_numeros(10, 2)
print(f"Resultado: {res}\n")

# Caso 2: División por cero
print("Caso 2:")
res = dividir_numeros(10, 0)
print(f"Resultado: {res}\n")

En este ejemplo, hemos definido una clase ExcepcionPersonalizada que hereda de Exception. La clase tiene un constructor personalizado que acepta dos argumentos: mensaje y codigo_error. El constructor llama al constructor de la clase base Exception con el argumento mensaje.

En la función dividir_numeros, levantamos nuestra ExcepcionPersonalizada cuando se intenta dividir por cero. Luego, manejamos la excepción en el bloque except usando un alias as e y accedemos a sus atributos personalizados, como codigo_error.

Para finalizar con el tema de las excepciones, vamos a ver un ejemplo completo donde se muestra cómo manejar múltiples excepciones en un solo bloque except. En este ejemplo, crearemos una función que divide dos números y maneja las excepciones ZeroDivisionError y TypeError en un único bloque except.

def dividir_numeros(numerador, denominador):
    try:
        resultado = numerador / denominador
    except (ZeroDivisionError, TypeError) as e:
        if isinstance(e, ZeroDivisionError):
            print("Error: No se puede dividir por cero")
        elif isinstance(e, TypeError):
            print("Error: Los argumentos deben ser números")
        return None
    else:
        print("La división se realizó con éxito")
        return resultado
    finally:
        print("Función dividir_numeros finalizada")

# Caso 1: División exitosa
print("Caso 1:")
res = dividir_numeros(10, 2)
print(f"Resultado: {res}\n")

# Caso 2: División por cero
print("Caso 2:")
res = dividir_numeros(10, 0)
print(f"Resultado: {res}\n")

# Caso 3: Argumentos no numéricos
print("Caso 3:")
res = dividir_numeros(10, "dos")
print(f"Resultado: {res}\n")

La función dividir_numeros intenta dividir dos números. Si se produce una excepción ZeroDivisionError o TypeError dentro del bloque try, el bloque except correspondiente manejará ambas excepciones. Utilizamos la palabra clave as para asociar la excepción capturada a la variable e, y luego utilizamos isinstance para determinar el tipo de excepción y manejarla adecuadamente.

Ejercicio: queremos crear un programa que consulte el valor que contiene una posición concreta de un vector de tamaño 10. Para ello necesitamos que el programa pregunte al usuario qué posición del vector quiere visualizar, si la posición que introduce está fuera de rango, capturaremos la excepción, mostraremos un mensaje de error y pediremos al usuario que vuelva a introducir otra posición.

# Crear un vector vacío
vector = []

# Rellenar el vector con los valores de sus posiciones
for i in range(10):
    vector.append(i)

while True:
    try:
        # Pedir al usuario la posición que quiere mostrar
        posicion = int(input("Ingrese la posición del vector que desea mostrar (entre 0 y 9) o -1 para salir: "))

        # Salir del bucle si el usuario ingresa -1
        if posicion == -1:
            break

        # Mostrar el valor en la posición ingresada
        print(f"Valor en la posición {posicion}: {vector[posicion]}")

    except IndexError:
        print("La posición ingresada está fuera de rango. Por favor, ingrese una posición válida entre 0 y 9.")

¿Qué ocurre si el usuario en lugar de introducir un número entero introduce cualquier otro tipo de entrada? Podemos añadir una excepción que recoja este tipo de eventualidad, y el programa quedaría de la siguiente manera.

# Crear un vector vacío
vector = []

# Rellenar el vector con los valores de sus posiciones
for i in range(10):
    vector.append(i)

while True:
    try:
        # Pedir al usuario la posición que quiere mostrar
        posicion = int(input("Ingrese la posición del vector que desea mostrar (entre 0 y 9) o -1 para salir: "))

        # Salir del bucle si el usuario ingresa -1
        if posicion == -1:
            break

        # Mostrar el valor en la posición ingresada
        print(f"Valor en la posición {posicion}: {vector[posicion]}")

    except IndexError:
        print("La posición ingresada está fuera de rango. Por favor, ingrese una posición válida entre 0 y 9.")

    except ValueError:
        print("Entrada inválida. Por favor, ingrese un número entero.")