Skip to content

5. Las excepciones

Ahora nos centraremos en las excepciones.

Image

5.1. script [exceptions_01]

El primer script ilustra la necesidad de manejar las excepciones.

# se provoca un error a propósito
x = 4 / 0

Provocamos un error a propósito para ver qué sucede (exceptions-01.py):

1
2
3
4
5
6
7
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py", line 2, in <module>
    x = 4 / 0
ZeroDivisionError: division by zero

Process finished with exit code 1

La línea 4 nos muestra:

  • el tipo de la excepción: ZeroDivisionError;
  • el mensaje de error asociado: división por cero. Está en inglés. Es algo que tal vez queramos cambiar.

Una regla fundamental es que debemos hacer todo lo posible por evitar las excepciones generadas por el intérprete de Python. Debemos manejar los errores nosotros mismos.

La sintaxis para el manejo de excepciones es la siguiente:


try:
    actions susceptibles de lancer une exception
except (Ex1, Ex2…) as ex:
    actions de gestion de l'exception [ex]
except (Ex11, Ex12…) as ex:
    actions de gestion de l'exception [ex]
finally:
   actions toujours exécutées qu'il y ait exception ou non

En el bloque try**, la ejecución de las acciones se detiene tan pronto como surge una excepción (error). En ese caso, la ejecución continúa con las acciones de una de las cláusulas **except:

  • línea 3: si la excepción [ex] que se produjo es de un tipo que pertenece a la tupla (Ex1, Ex2…) o derivado de alguno de ellos, entonces se ejecutan las acciones de la línea 4;
  • línea 5: si la excepción no ha sido interceptada por la línea 3 y existe otra cláusula [except], entonces se lleva a cabo el mismo proceso. Etc…;
  • pueden existir tantas cláusulas [except] como sean necesarias para manejar los distintos tipos de excepción que puedan producirse en el [try];
  • si la excepción no ha sido manejada por ninguna de las cláusulas [except], entonces se propagará al código que la invocó. Si este se encuentra a su vez dentro de una estructura try/except, la excepción se maneja nuevamente; de lo contrario, continúa propagándose por la cadena de métodos invocados. En última instancia, llega al intérprete de Python. Este detiene entonces el programa en ejecución y muestra un mensaje de error como el que se muestra en el ejemplo anterior. La regla es, por lo tanto, que el programa principal debe interceptar todas las excepciones que puedan propagarse desde los métodos llamados;
  • línea 7: la cláusula [finally] siempre se ejecuta, independientemente de si hubo una excepción (continuación del «except») o no (continuación del «try»). Esto es así incluso si se produjo una excepción y esta no fue interceptada. En este caso, la cláusula [finally] se ejecutará antes de que la excepción se transmita al código que la invocó;

Una excepción lleva consigo información sobre el error que se produjo. Esta información se puede obtener con la siguiente sintaxis:


except MyException as exception:

[exception] es la excepción que se produjo. [exception.args] representa la tupla de parámetros de la excepción.

Para lanzar una excepción, se utiliza la sintaxis


raise MyException(param1, param2…)

donde, en la mayoría de los casos, MyException es una clase derivada de la clase BaseException. Los parámetros pasados al constructor de la clase estarán disponibles en la cláusula except de las estructuras de interceptación de excepciones con la sintaxis **[ex.args] si **[ex] es la excepción interceptada por la cláusula **[except]**.

Estos conceptos se ilustran en el siguiente script.

5.2. script [exceptions_02]

El siguiente script maneja los errores de manera explícita:


# manejo de excepciones
essai = 0

# se provoca un error y se gestiona
x = 2
try:
    x = 4 / 0
except ZeroDivisionError as erreur:
    # el error es la excepción interceptada
    print(f"essai n° {essai} : {erreur}")
# el valor de x no ha cambiado
print(f"x={x}")

# Se vuelve a intentar
essai += 1
try:
    x = 4 / 0
except BaseException as erreur:
    # se intercepta la excepción más general
    # el error es la excepción interceptada
    print(f"essai n° {essai} : {erreur}")

# se pueden interceptar diferentes tipos de excepciones
# la ejecución se detiene en el primer [except] capaz de manejar la excepción
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # esta excepción no ocurre aquí
    print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
    # se intercepta la excepción más general
    print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
    # se intercepta un tipo específico
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")

# volvemos a intentarlo cambiando el orden 
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # esta excepción no ocurre aquí
    print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
    # se intercepta un tipo específico
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
    # se intercepta la excepción más general
    print(f"essai n° {essai} : (Exception) {erreur}")

# una cláusula «except» sin argumentos
essai += 1
try:
    x = 4 / 0
except:
    # no nos interesa la naturaleza de la excepción
    print(f"essai n° {essai} : il y a eu un problème")

# otro tipo de excepción
essai += 1
try:
    # x no se puede convertir en un número entero
    x = int("x")
except ValueError as erreur:
    # error es la excepción interceptada
    print(f"essai n° {essai} : {erreur}")

# una excepción transporta información en una tupla accesible para el programa
essai += 1
try:
    x = int("x")
except ValueError as erreur:
    # error es la excepción interceptada
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# se pueden lanzar excepciones
essai += 1
try:
    raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
    # el error es la excepción interceptada
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")


# se pueden crear excepciones propias
# deben derivarse de la clase [BaseException]
class MyError(BaseException):
    pass


# se lanza la excepción MyError
essai += 1
try:
    raise MyError("info1", "info2", "info3")
except MyError as erreur:
    # el error es la excepción interceptada
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

# se lanza la excepción MyError con un mensaje de error
essai += 1
try:
    raise MyError("mon msg d'erreur")
except MyError as erreur:
    # el error es la excepción interceptada
    print(f"essai n° {essai} : {erreur.args[0]}")

# la cláusula finally siempre se ejecuta
# independientemente de si haya una excepción o no
essai += 1
x = None
try:
    x = 1
except:
    # excepción
    print(f"essai n° {essai} : exception")
finally:
    # ejecutado en todos los casos
    print(f"essai n° {essai} : finally x={x}")

essai += 1
x = None
try:
    x = 2 / 0
except:
    # excepción
    print(f"essai n° {essai} : exception")
finally:
    # se ejecuta en todos los casos
    print(f"essai n° {essai} : finally x={x}")

# no es obligatorio incluir una cláusula [except]
essai += 1
try:
    # provoca un error
    x = 4 / 0
finally:
    # se ejecuta en todos los casos
    print(f"essai n° {essai} : finally x={x}")

Notas:

  • líneas 4-12: se maneja una división por cero;
  • línea 8: se intercepta la excepción exacta que se produce y se muestra;
  • línea 12: debido a la excepción que se produjo, x no recibió ningún valor en la línea 7 y, por lo tanto, no cambió de valor;
  • líneas 14-21: se repite el mismo proceso, pero interceptando una excepción de nivel superior del tipo BaseException. Como la excepción ZeroDivisionError deriva de la clase BaseException, la cláusula except la detendrá;
  • líneas 23-36: se incluyen varias cláusulas except para manejar varios tipos de excepción. Se ejecutará una sola cláusula except o ninguna si la excepción no cumple con ninguna cláusula «except»;
  • líneas 38-50: se repite el mismo proceso cambiando el orden de las cláusulas [except] para mostrar su función;
  • líneas 52-58: la cláusula except puede no tener ningún argumento. En ese caso, detiene todas las excepciones;
  • líneas 60-67: introducen la excepción ValueError;
  • líneas 69-75: se recupera la información que transporta la excepción;
  • líneas 77-83: muestran cómo lanzar (raise) una excepción;
  • líneas 86-106: ilustran el uso de una clase de excepción propia MyError. La clase MyError simplemente deriva de la clase base BaseException. No agrega nada a su clase base. Pero ahora puede ser nombrada explícitamente en las cláusulas «except»;
  • líneas 108-130: ilustran el uso de la cláusula finally;
  • líneas 132-139: estas líneas muestran que la cláusula [except] no es obligatoria;

Los resultados en pantalla son los siguientes:

C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py", line 136, in <module>
    x = 4 / 0
ZeroDivisionError: division by zero
essai n° 0 : division by zero
x=2
essai n° 1 : division by zero
essai n° 2 : (Exception) division by zero
essai n° 3 : (ZeroDivisionError) division by zero
essai n° 4 : il y a eu un problème
essai n° 5 : invalid literal for int() with base 10: 'x'
essai n° 6 : invalid literal for int() with base 10: 'x', paramètres=("invalid literal for int() with base 10: 'x'",)
essai n° 7 : ('param1', 'param2', 'param3'), paramètres=('param1', 'param2', 'param3')
essai n° 8 : ('info1', 'info2', 'info3'), paramètres=('info1', 'info2', 'info3')
essai n° 9 : mon msg d'erreur
essai n° 10 : finally x=1
essai n° 11 : exception
essai n° 11 : finally x=None
essai n° 12 : finally x=None

Process finished with exit code 1

5.3. script [exceptions_03]

Este nuevo script ilustra cómo se transmiten las excepciones a lo largo de la cadena de funciones que las invocan:


# una excepción propia
class MyError(BaseException):
    pass


# tres funciones
def f1(x: int) -> int:
    # no se manejan las excepciones; se propagan automáticamente
    return f2(x)


def f2(y: int) -> int:
    # no se manejan las excepciones; se propagan automáticamente
    return f3(y)


def f3(z: int) -> int:
    if (z % 2) == 0:
        # si z es par, se lanza una excepción
        raise MyError("exception dans f3")
    else:
        return 2 * z


# ---------- main

# las excepciones se propagan a lo largo de la cadena de métodos llamados
# hasta que un método las intercepte. En este caso, será main
try:
    print(f1(4))
except MyError as erreur:
    print(f"type : {type(erreur)}, arguments : {erreur.args}")


# otras tres funciones que enriquecen las excepciones que se transmiten
def f4(x: int) -> int:
    try:
        return f5(x)
    except MyError as erreur:
        # se enriquece la excepción y luego se vuelve a lanzar
        raise MyError("exception dans f4", erreur)


def f5(y: int) -> int:
    try:
        return f6(y)
    except MyError as erreur:
        # se enriquece la excepción y luego se vuelve a lanzar
        raise MyError("exception dans f5", erreur)


def f6(z: int) -> int:
    if (z % 2) == 0:
        # se lanza una excepción si z es par
        raise MyError("exception dans f6")
    else:
        return 2 * z


# ---------- main
try:
    print(f4(4))
except MyError as erreur:
    # se muestra la excepción
    print(f"type : {type(erreur)}, arguments : {erreur.args}")
    # se puede recorrer la pila de excepciones
    err = erreur
    # se muestra el mensaje de error
    print(err.args[0])
    # ¿Está encapsulada una excepción?
    while len(err.args) == 2 and isinstance(err.args[1], BaseException):
        # cambio de excepción
        err = err.args[1]
        # el primer argumento es el mensaje de error
        print(err.args[0])

Notas:

  • líneas 25-32, en la llamada main --> f1 --> f2 --> f3 (línea 30), la excepción MyError lanzada por f3 se propagará hasta main. A continuación, será procesada por la cláusula except de la línea 31;
  • líneas 61-75: en la llamada main --> f4 --> f5 --> f6 (línea 62), la excepción MyError lanzada por f6 se propagará hasta main. A continuación, será procesada por la cláusula except de la línea 63. En esta ocasión, al remontar por la cadena de funciones llamantes, la excepción MyError que se remonta está a su vez encapsulada en otra excepción;
  • líneas 66-75: muestran cómo ascender por la pila de excepciones;
  • línea 71: la función [isinstance(instance, Classe)] devuelve True si el objeto [instance] es de tipo [Classe] o un derivado. Aquí hemos utilizado la excepción de nivel más alto, [BaseException], lo que nos asegura recuperar todas las excepciones;

Los resultados en pantalla son los siguientes:

1
2
3
4
5
6
7
8
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_03.py
type : <class '__main__.MyError'>, arguments : ('exception dans f3',)
type : <class '__main__.MyError'>, arguments : ('exception dans f4', MyError('exception dans f5', MyError('exception dans f6')))
exception dans f4
exception dans f5
exception dans f6

Process finished with exit code 0