5. Las excepciones
Ahora nos centramos en las excepciones.

5.1. script [exceptions_01]
El primer script ilustra la necesidad de gestionar las excepciones.
Provocamos un error a propósito para ver qué pasa (exceptions-01.py):
La línea 4 nos da:
- el tipo de excepción: ZeroDivisionError;
- el mensaje de error asociado: división por cero. Está en inglés. Es algo que quizá queramos cambiar.
Una regla esencial es que debemos hacer todo lo posible por evitar las excepciones generadas por el intérprete de Python. Debemos gestionar los errores nosotros mismos.
La sintaxis de la gestión 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 try, la ejecución de las acciones se detiene en cuanto surge una excepción (error). En este 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 ha producido es de un tipo que pertenece a la tupla (Ex1, Ex2…) o derivado de uno 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…;
- puede haber tantas cláusulas [except] como sean necesarias para gestionar los diferentes tipos de excepción que puedan producirse en el [try];
- si la excepción no ha sido tratada por ninguna de las cláusulas [except], entonces se propagará al código llamante. Si este se encuentra a su vez dentro de una estructura try / except, la excepción se gestiona de nuevo; de lo contrario, continúa propagándose por la cadena de métodos llamados. En última instancia, llega al intérprete de Python. Este detiene entonces el programa en ejecución y muestra un mensaje de error del tipo mostrado en el ejemplo anterior. La regla es, por tanto, que el programa principal debe interceptar todas las excepciones que puedan remontarse desde los métodos llamados;
- línea 7: la cláusula [finally] siempre se ejecuta, tanto si ha habido una excepción (continuación del except) como si no (continuación del try). Esto es así incluso si se ha producido una excepción y no ha sido interceptada. En este caso, la cláusula [finally] se ejecutará antes de que la excepción se transmita al código llamante;
Una excepción lleva consigo información sobre el error que se ha producido. Se puede obtener mediante la siguiente sintaxis:
except MyException as exception:
[exception] es la excepción que se ha producido. [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 gestiona los errores de forma explícita:
# gestión 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 empezar
essai += 1
try:
x = 4 / 0
except BaseException as erreur:
# se intercepta la excepción más general
# 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 gestionar la excepción
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# esta excepción no se produce 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 concreto
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
# volvemos a empezar cambiando el orden
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# esta excepción no se produce 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:
# 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
# haya o no una excepción
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:
# ejecutado en todos los casos
print(f"essai n° {essai} : finally x={x}")
# no es obligatorio incluir una cláusula [except]
essai += 1
try:
# se produce un error
x = 4 / 0
finally:
# ejecutado en todos los casos
print(f"essai n° {essai} : finally x={x}")
Notas:
- líneas 4-12: se gestiona 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 ha producido, x no ha recibido ningún valor en la línea 7 y, por lo tanto, no ha cambiado de valor;
- líneas 14-21: se repite el mismo proceso, pero interceptando una excepción de nivel superior de tipo BaseException. Dado que 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 gestionar varios tipos de excepción. Se ejecutará una sola cláusula except o ninguna si la excepción no cumple ninguna cláusula except;
- líneas 38-50: se repite lo mismo cambiando el orden de las cláusulas [except] para mostrar la función de esta;
- 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 transportada por la excepción;
- líneas 77-83: introducen la forma de lanzar (raise) una excepción;
- líneas 86-106: ilustran el uso de una clase de excepción propia MyError. La clase MyError se limita a derivar de la clase base BaseException. No añade nada a su clase base. Pero ahora puede nombrarse 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:
5.3. script [exceptions_03]
Este nuevo script ilustra la propagación de excepciones en la cadena de funciones llamantes:
# una excepción propia
class MyError(BaseException):
pass
# tres funciones
def f1(x: int) -> int:
# no se gestionan las excepciones: se transmiten automáticamente
return f2(x)
def f2(y: int) -> int:
# no se gestionan las excepciones: se transmiten 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 transmiten a lo largo de la cadena de métodos llamados
# hasta que un método la 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 remite
def f4(x: int) -> int:
try:
return f5(x)
except MyError as erreur:
# enriquecemos la excepción y luego la relanzamos
raise MyError("exception dans f4", erreur)
def f5(y: int) -> int:
try:
return f6(y)
except MyError as erreur:
# enriquecemos la excepción y luego la relanzamos
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:
# visualización de 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á tratada 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 lanzada MyError por f6 se remontará hasta main. A continuación, será tratada 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 remontar 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 garantiza recuperar todas las excepciones;
Los resultados en pantalla son los siguientes: