Apéndice Q — Gestión de errores y excepciones.

Objetivo. Revisar la forma de gestionar errores y excepciones

Q.1 Tipos de errores:

En Python existen dos tipos de errores por los cuales un programa se detiene y no continua con su ejecución normal.

Q.1.1 Errores de sintaxis.

Ocurren cuando no se escriben correctamente las expresiones y declaraciones como lo especifica la sintaxis de Python.

Por ejemplo:

# Escribimos a propósito 'printf' que es un nombre incorrecto.
printf('Hola mundo!')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[2], line 2
      1 # Escribimos a propósito 'printf' que es un nombre incorrecto.
----> 2 printf('Hola mundo!')

NameError: name 'printf' is not defined
  • Observa que el tipo de error se imprime cuando éste ocurre.
  • En el caso anterior el error fue de tipo NameError, por lo que hay que revisar que todo esté correctamente escrito.

Q.1.2 Errores provocados por excepciones.

Son errores lógicos que detienen la ejecución de un programa aún cuando la sintaxis sea la correcta.

Por ejemplo:

def raizCuadrada(numero):
    numero = float(numero)
    print("La raíz cuadrada del número {} es {}".format(numero, numero ** 0.5))
# Ejemplo correcto que se ejecuta sin problemas.
raizCuadrada(2)
La raíz cuadrada del número 2.0 es 1.4142135623730951
# Ejemplo correcto, se calcula la raíz cuadrada de -1.
# El resultado es un número complejo. En este caso Python
# se encarga de realizar las conversiones necesarias.
raizCuadrada(-1)
La raíz cuadrada del número -1.0 es (6.123233995736766e-17+1j)
# Ejemplo incorrecto. No es posible calcular la raíz cuadrada
# de un número complejo, es una operación no definida.
raizCuadrada(1+1j)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[6], line 3
      1 # Ejemplo incorrecto. No es posible calcular la raíz cuadrada
      2 # de un número complejo, es una operación no definida.
----> 3 raizCuadrada(1+1j)

Cell In[3], line 2, in raizCuadrada(numero)
      1 def raizCuadrada(numero):
----> 2     numero = float(numero)
      3     print("La raíz cuadrada del número {} es {}".format(numero, numero ** 0.5))

TypeError: float() argument must be a string or a real number, not 'complex'

En el ejemplo anterior se produce un error de tipo TypeError, es decir hay incompatibilidad con los tipos de datos que se están manipulando.

# Ejemplo incorrecto. No se puede calcular la raíz cuadrada
# de una cadena.
raizCuadrada("hola")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[7], line 3
      1 # Ejemplo incorrecto. No se puede calcular la raíz cuadrada
      2 # de una cadena.
----> 3 raizCuadrada("hola")

Cell In[3], line 2, in raizCuadrada(numero)
      1 def raizCuadrada(numero):
----> 2     numero = float(numero)
      3     print("La raíz cuadrada del número {} es {}".format(numero, numero ** 0.5))

ValueError: could not convert string to float: 'hola'

En el ejemplo anterior se produce un error de tipo ValueError, es decir es decir hay un problema con el contenido del objeto.

Q.2 Gestión de excepciones con: try, except, finally

Los errores que se pueden manejar son aquellos errores lógicos como los presentados anteriormente en donde es posible “predecir” el tipo de error que puede ocurrir de acuerdo con la implementación que estamos realizando.

Todas las excepciones en Python son objetos de una clase que se derivan de la clase principal BaseExcepcion. Más detalles se pueden consultar aquí.

Las excepciones se pueden capturar y manejar adecuadamente. Para ello se tienen las siguientes herramientas:

  • try
  • except
  • else
  • finally

Cuando se identifica una sección de código susceptible de errores, ésta puede ser delimitada con la expresión try. Cualquier excepción que ocurra dentro de esta sección de código podrá ser capturada y gestionada.

La expresión except es la encargada de gestionar las excepciones que se capturan. Si se utiliza sin mayor información, ésta ejecutará el código que contiene para todas las excepciones que ocurran.

En el ejemplo de la función raizCuadrada() podemos manejar las excepciones como sigue:

def raizCuadrada(numero):
    """
    Función que calcula la raíz cuadrada de un número.

    Parameters
    ----------
    numero: int o float
        Valor al que se le desea calcular la raíz cuadrada.
    
    """
    # Intenta realizar el cálculo que está dentro de try
    try:
        numero = float(numero)
        print(f"La raíz cuadrada del número {numero} es {numero**0.5}")

    # Si ocurre una excepción se captura aquí
    except:
        # No se hace nada con la excepción (por el momento)
        pass

    print("Gracias por usar Python!.")

Usando la nueva versión de la función raizCuadrada() intentemos ejecutarla con los siguientes ejemplos:

raizCuadrada(1)
La raíz cuadrada del número 1.0 es 1.0
Gracias por usar Python!.
raizCuadrada(-1)
La raíz cuadrada del número -1.0 es (6.123233995736766e-17+1j)
Gracias por usar Python!.
raizCuadrada(1+1j)
Gracias por usar Python!.
raizCuadrada("hola")
Gracias por usar Python!.

Observa que aún con argumentos erróneos no se genera ningún mensaje de error. Esto se debe a que la cláusula except captura todas las posibles excepciones, pero no hace nada.

Q.2.1 Gestión general de las excepciones

Ya que sabemos como capturar las excepciones, veamos cómo pueden ser tratadas para dar retroalimentación al usuario.

def raizCuadrada(numero):
    """
    Función que calcula la raíz cuadrada de un número.

    Parameters
    ----------
    numero: int o float
        Valor al que se le desea calcular la raíz cuadrada.
    
    """
    # Variable Booleana para manejar las excepciones.
    ocurre_error = False
    
    # Intenta realizar el cálculo que está dentro de try    
    try:
        numero = float(numero)
        print("La raíz cuadrada del número {} es {}".format(numero, numero ** 0.5))

    # Si ocurre una excepción se captura en el except
    except:
        ocurre_error = True

    # Cuando ocurre un error se hace lo siguiente:
    if ocurre_error:
        print("Cuidado, hubo una falla en el programa, no se pudo realizar el cálculo")
    else:
        print("Gracias por usar Python!.")
raizCuadrada(1)
La raíz cuadrada del número 1.0 es 1.0
Gracias por usar Python!.
raizCuadrada(-1)
La raíz cuadrada del número -1.0 es (6.123233995736766e-17+1j)
Gracias por usar Python!.
raizCuadrada(1+1j)
Cuidado, hubo una falla en el programa, no se pudo realizar el cálculo
raizCuadrada("hola")
Cuidado, hubo una falla en el programa, no se pudo realizar el cálculo

Observa que ahora se avisa al usuario que hubo un error al ejecutar la función por lo que el cálculo no se realizó. Sin embargo hace falta más información.

Q.2.2 Gestión de las excepciones por su tipo.

La expresión except puede ser utilizada de forma tal que ejecute código dependiendo del tipo de error que ocurra. En este caso sabemos que pueden ocurrir dos tipos de errores: TypeError y ValueError. Entonces la nueva versión de la función raizCuadrada() es como sigue:

def raizCuadrada(numero):
    """
    Función que calcula la raíz cuadrada de un número.

    Parameters
    ----------
    numero: int o float
        Valor al que se le desea calcular la raíz cuadrada.
    
    """
    # Variable Booleana para manejar las excepciones.
    ocurre_error = False
    
    # Intenta realizar el cálculo que está dentro de try
    try:
        numero = float(numero)
        print("La raíz cuadrada del número {} es {}".format(numero, numero ** 0.5))

    # En esta sección se trata la excepción de tipo TypeError
    except TypeError:
        ocurre_error = True
        print("Ocurrió un error de tipo: TypeError. \n Verifique que los tipos sean compatibles.")

    # En esta sección se trata la excepción de tipo ValueError
    except ValueError:
        ocurre_error = True
        print("Ocurrió un error de tipo ValueError. \n Verifique el contenido de los argumentos.")

    # En esta sección se tratan todas las otras posible excepciones
    except:
        ocurre_error = True
        print("Ocurrió algo misterioso")
        
    # Cuando ocurre un error se hace lo siguiente:
    if ocurre_error:
        print("Hubo una falla en el programa, no se pudo realizar el cálculo")
    else:
        print("Gracias por usar Python!.")
raizCuadrada(1)
La raíz cuadrada del número 1.0 es 1.0
Gracias por usar Python!.
raizCuadrada(-1)
La raíz cuadrada del número -1.0 es (6.123233995736766e-17+1j)
Gracias por usar Python!.
raizCuadrada(1+4j)
Ocurrió un error de tipo: TypeError. 
 Verifique que los tipos sean compatibles.
Hubo una falla en el programa, no se pudo realizar el cálculo
raizCuadrada("hola")
Ocurrió un error de tipo ValueError. 
 Verifique el contenido de los argumentos.
Hubo una falla en el programa, no se pudo realizar el cálculo

Observa que ya se da mayor información sobre el tipo de error que ocurrió y el usuario puede saber que hacer para corregir los errores.

Todos los tipos de errores que existen en Python se pueden consultar en Concrete exceptions .

Q.2.3 Información del error

Se puede capturar toda la información del error para pasarla al usuario. Esto se hace como sigue:

def raizCuadrada(numero): 
    """
    Función que calcula la raíz cuadrada de un número.

    Parameters
    ----------
    numero: int o float
        Valor al que se le desea calcular la raíz cuadrada.
    
    """
    # Variable Booleana para manejar las excepciones.
    ocurre_error = False
    
    # Intenta realizar el cálculo que está dentro de try
    try:
        numero = float(numero)
        print("La raíz cuadrada del número {} es {}".format(numero, numero ** 0.5))

    # En esta sección se trata la excepción de tipo TypeError y se obtienen los detalles  
    except TypeError as info:
        ocurre_error = True
        print("Ocurrió un error (TypeError):\n", info)

    # En esta sección se trata la excepción de tipo ValueError y se obtienen los detalles  
    except ValueError as info:
        ocurre_error = True
        print("Ocurrió un error (ValueError):\n", info)

    # En esta sección se tratan todas las otras posible excepciones
    except:
        ocurre_error = True
        print("Ocurrió algo misterioso")

    # Cuando ocurre un error se hace lo siguiente:
    if ocurre_error:
        print("Hubo una falla en el programa, no se pudo realizar el cálculo")
    else:
        print("Gracias por usar Python!.")
raizCuadrada(1)
La raíz cuadrada del número 1.0 es 1.0
Gracias por usar Python!.
raizCuadrada(-1)
La raíz cuadrada del número -1.0 es (6.123233995736766e-17+1j)
Gracias por usar Python!.
raizCuadrada(1+4j)
Ocurrió un error (TypeError):
 float() argument must be a string or a real number, not 'complex'
Hubo una falla en el programa, no se pudo realizar el cálculo
raizCuadrada("hola")
Ocurrió un error (ValueError):
 could not convert string to float: 'hola'
Hubo una falla en el programa, no se pudo realizar el cálculo

Observa que ahora además de conocer el tipo de error, también se muestra toda la información del error para que el usuario tome las acciones pertinentes.

Q.2.4 finally

Esta sección se ejecuta siempre, sin importar si hubo una excepción o no.

def raizCuadrada(numero):
    """
    Función que calcula la raíz cuadrada de un número.

    Parameters
    ----------
    numero: int o float
    Valor al que se le desea calcular la raíz cuadrada.
    
    """
    # Variable Booleana para manejar las excepciones.
    ocurre_error = False
    
    # Intenta realizar el cálculo que está dentro de try
    try:
        numero = float(numero)
        print("La raíz cuadrada del número {} es {}".format(numero, numero ** 0.5))

    # En esta sección se trata la excepción de tipo TypeError y se obtienen los detalles  
    except TypeError as info:
        ocurre_error = True
        print("Ocurrió un error (TypeError):\n", info)

    # En esta sección se trata la excepción de tipo ValueError y se obtienen los detalles  
    except ValueError as info:
        ocurre_error = True
        print("Ocurrió un error (ValueError):\n", info)

    # En esta sección se tratan todas las otras posible excepciones
    except:
        ocurre_error = True
        print("Ocurrió algo misterioso")

    # Cuando ocurre un error se hace lo siguiente:
    finally:
        if ocurre_error:
            print("Hubo una falla en el programa, no se pudo realizar el cálculo")
        else:
            print("Gracias por usar Python!.")
raizCuadrada(1)
La raíz cuadrada del número 1.0 es 1.0
Gracias por usar Python!.
raizCuadrada(-1)
La raíz cuadrada del número -1.0 es (6.123233995736766e-17+1j)
Gracias por usar Python!.
raizCuadrada(1+4j)
Ocurrió un error (TypeError):
 float() argument must be a string or a real number, not 'complex'
Hubo una falla en el programa, no se pudo realizar el cálculo
raizCuadrada("hola")
Ocurrió un error (ValueError):
 could not convert string to float: 'hola'
Hubo una falla en el programa, no se pudo realizar el cálculo

Q.2.5 Lanzar excepciones controladas.

Es posible presentar toda la información que genera la excepción y agregarle notas para el usuario. Para agregar notas usamos el método add_note() y para lanzar la excepción una vez controlada usamos raise. La siguiente versión de la función raizCuadrada() tiene al final una cláusula else, la cual se ejecuta cuando no ocurre ninguna excepción. En este caso, dentro del try realizamo el cálculo de la raíz cuadrada y en el else hacemos la impresión del resultado.

def raizCuadrada(numero):
    """
    Función que calcula la raíz cuadrada de un número.

    Parameters
    ----------
    numero: int o float
        Valor al que se le desea calcular la raíz cuadrada.
    
    """
    
    # Intenta realizar el cálculo que está dentro de try
    try:
        numero_cuadrado = float(numero) ** 0.5

    # En esta sección se trata la excepción de tipo TypeError y se obtienen los detalles  
    except TypeError as info:
        info.add_note("\n" + "-"*20)
        info.add_note(f"raizCuadrada{numero}: Para calcular una raíz cuadrada, el argumento 'numero' debe ser compatible con un int o un float")
        info.add_note("-"*20)
        raise # Lanzamos la excepción con toda la información

    # En esta sección se trata la excepción de tipo ValueError y se obtienen los detalles  
    except ValueError as info:
        info.add_note("\n" + "-"*20)
        info.add_note(f"raizCuadrada('{numero}'): Para calcular una raíz cuadrada, el valor del argumento 'numero' debe ser compatible con un int o un float")
        info.add_note("-"*20)
        raise # Lanzamos la excepción con toda la información
        
    # En esta sección se tratan todas las otras posible excepciones
    except:
        print("Ocurrió algo misterioso")

    else:
        print("La raíz cuadrada del número {} es {}".format(numero, numero_cuadrado))
raizCuadrada(1)
La raíz cuadrada del número 1 es 1.0
raizCuadrada(-1)
La raíz cuadrada del número -1 es (6.123233995736766e-17+1j)
raizCuadrada(1+4j)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[42], line 1
----> 1 raizCuadrada(1+4j)

Cell In[39], line 14, in raizCuadrada(numero)
     12 # Intenta realizar el cálculo que está dentro de try
     13 try:
---> 14     numero_cuadrado = float(numero) ** 0.5
     16 # En esta sección se trata la excepción de tipo TypeError y se obtienen los detalles  
     17 except TypeError as info:

TypeError: float() argument must be a string or a real number, not 'complex'

--------------------
raizCuadrada(1+4j): Para calcular una raíz cuadrada, el argumento 'numero' debe ser compatible con un int o un float
--------------------
raizCuadrada("hola")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[43], line 1
----> 1 raizCuadrada("hola")

Cell In[39], line 14, in raizCuadrada(numero)
     12 # Intenta realizar el cálculo que está dentro de try
     13 try:
---> 14     numero_cuadrado = float(numero) ** 0.5
     16 # En esta sección se trata la excepción de tipo TypeError y se obtienen los detalles  
     17 except TypeError as info:

ValueError: could not convert string to float: 'hola'

--------------------
raizCuadrada('hola'): Para calcular una raíz cuadrada, el valor del argumento 'numero' debe ser compatible con un int o un float
--------------------