Skip to content

6. Las excepciones

Ahora nos centramos en las excepciones.

  

Programa (exceptions_01)

El primer script ilustra la necesidad de gestionar las excepciones.


# -*- coding=utf-8 -*-

# se produce un error
x=4/0

Provocamos un error a propósito para ver la información que genera el intérprete. El resultado en pantalla es el siguiente:

1
2
3
4
Traceback (most recent call last):
  File "D:\data\istia-1112\python\tutoriel\exceptions_01.py", line 4, in <module>
    x=4/0
ZeroDivisionError: integer division or modulo by zero

La línea 4 nos da:

  • el tipo de excepción: ZeroDivisionError;
  • el mensaje de error asociado: integer division or modulo by zero. Está en inglés. Es algo que quizá queramos cambiar.

La regla fundamental en programación es que hay que hacer todo lo posible para evitar fallos «salvajes» como el anterior. Incluso en caso de error, el programa debe terminarse correctamente proporcionando información sobre el error que se ha producido.

La sintaxis de la gestión de excepciones es la siguiente:


try:
    actions susceptibles de lancer une exception
except [classe d'exception, ...]:
    actions de gestion de l'exception
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 en cuanto se produce una excepción. En ese caso, la ejecución continúa con las acciones de la cláusula «except».

La cláusula


except [MyException, ...]:

intercepta las excepciones de tipo MyException o derivadas. Cuando se produce una excepción en un try, el intérprete examina las cláusulas except asociadas al try en el orden en que se han escrito. Se detiene en la primera cláusula except que permite tratar la excepción que se ha producido. Si no encuentra ninguna, la excepción se remite al método que la invocó. Si este tiene un try / except, la excepción se gestiona de nuevo; de lo contrario, continúa remontando 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 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.

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


except MyException as informations:

información es una tupla que contiene la información relacionada con la excepción.

La sintaxis


except MyException, erreur:

asigna a la variable «error» el mensaje de error relacionado con 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 Exception. 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.

Estos conceptos se ilustran en el siguiente script.


Programa (exceptions_02)

El siguiente script gestiona los errores de forma explícita:


# -*- coding=utf-8 -*-

i=0
# se provoca un error y se gestiona
x=2
try:
    x=4/0
except ZeroDivisionError, erreur:
    print ("%s : %s ") % (i, erreur)
# el valor de x no ha cambiado
print "x=%s" % (x)

# volvemos a empezar
i+=1
try:
    x=4/0
except Exception, erreur:
    # se intercepta la excepción más general
    print ("%s : %s ") % (i, erreur)

# se pueden interceptar varios tipos de excepciones 
i+=1
try:
    x=4/0
except ValueError, erreur:
    # esta excepción no se produce aquí
    print ("%s : %s ") % (i, erreur)
except Exception, erreur:
    # se intercepta la excepción más general
    print ("%s : (Exception) %s ") % (i, erreur)
except ZeroDivisionError, erreur:
    # se intercepta un tipo concreto
    print ("%s : (ZeroDivisionError) %s ") % (i, erreur)

# volvemos a empezar cambiando el orden 
i+=1
try:
    x=4/0
except ValueError, erreur:
    # esta excepción no se produce aquí
    print ("%s : %s ") % (i, erreur)
except ZeroDivisionError, erreur:
    # se intercepta un tipo específico
    print ("%s : (ZeroDivisionError) %s ") % (i, erreur)
except Exception, erreur:
    # se intercepta la excepción más general
    print ("%s : (Exception) %s ") % (i, erreur)

# una cláusula except sin argumentos
i+=1
try:
    x=4/0
except:
    # no nos interesa la naturaleza de la excepción ni el mensaje de error
    print ("%s : il y a eu un probleme ") % (i)

# otro tipo de excepción
i+=1
try:
    x=int("x")
except ValueError, erreur:
    print ("%s : %s ") % (i, erreur)

# una excepción transporta información en una tupla accesible para el programa
i+=1
try:
    x=int("x")
except ValueError as infos:
    print ("%s : %s ") % (i, infos)

# se pueden lanzar excepciones
i+=1
try:
    raise ValueError("param1","param2","param3")
except ValueError as infos:
    print ("%s : %s ") % (i, infos)

# se pueden crear excepciones propias
class MyError(Exception):
    pass

# se lanza la excepción MyError
i+=1
try:
    raise MyError("info1","info2", "info3")
except MyError as infos:
    print ("%s : %s ") % (i, infos)

# se lanza la excepción MyError
i+=1
try:
    raise MyError("mon msg d'erreur")
except MyError, erreur:
    print ("%s : %s ") % (i, erreur)

# se puede lanzar cualquier tipo de objeto
class Objet:
    def __init__(self,msg):
        self.msg=msg
    def __str__(self):
        return self.msg

i+=1
try:
    raise Objet("pb...")
except Objet as erreur:
    print "%s : %s" % (i, erreur)

# la cláusula finally siempre se ejecuta
# haya o no una excepción
i+=1
x=None
try:
    x=1
except:
    print "%s : exception" % (i)
finally:
    print "%s : finally x=%s" % (i,x)

i+=1
x=None
try:
    x=2/0
except:
    print "%s : exception" % (i)
finally:
    print "%s : finally x=%s" % (i,x)

Notas:

  • líneas 6-9: se gestiona una división por cero;
  • línea 8: se intercepta la excepción exacta que se produce;
  • línea 11: debido a la excepción que se ha producido, x no ha recibido ningún valor y, por lo tanto, no ha cambiado de valor;
  • líneas 15-19: se repite lo mismo, pero interceptando una excepción de nivel superior de tipo Exception. Como la excepción ZeroDivisionError deriva de la clase Exception, la cláusula except la detendrá;
  • líneas 23-33: se incluyen varias cláusulas except para gestionar varios tipos de excepción. Solo se ejecutará una cláusula except o ninguna si la excepción no cumple ninguna cláusula except;
  • líneas 51-55: la cláusula except puede no tener ningún argumento. En ese caso, detiene todas las excepciones;
  • líneas 59-62: introducen la excepción ValueError;
  • líneas 66-69: se recupera la información transportada por la excepción;
  • líneas 73-76: introducen la forma de lanzar una excepción;
  • líneas 79-84: ilustran el uso de una clase de excepción propia MyError;
  • líneas 79-80: la clase MyError se limita a derivar de la clase base Exception. No añade nada a su clase base. Pero ahora puede nombrarse explícitamente en las cláusulas except;
  • líneas 97-107: ilustran que en Python se puede lanzar cualquier tipo de objeto y no solo objetos derivados de la clase Exception;
  • líneas 109-127: ilustran el uso de la cláusula finally.

Los resultados en pantalla son los siguientes:

0 : integer division or modulo by zero
x=2
1 : integer division or modulo by zero
2 : (Exception) integer division or modulo by zero
3 : (ZeroDivisionError) integer division or modulo by zero
4 : il y a eu un probleme
5 : invalid literal for int() with base 10: 'x'
6 : invalid literal for int() with base 10: 'x'
7 : ('param1', 'param2', 'param3')
8 : ('info1', 'info2', 'info3')
9 : mon msg d'erreur
10 : pb...
11 : finally x=1
12 : exception
12 : finally x=None

Programa (exceptions_03)

Este nuevo script ilustra el reenvío de excepciones en la cadena de métodos llamantes:


# -*- coding=utf-8 -*-

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

# tres métodos
def f1(x):
    # no se gestionan las excepciones: se propagarán automáticamente
    return f2(x)

def f2(y):
    # no se gestionan las excepciones: se transmiten automáticamente
    return f3(y)

def f3(z):
    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 las intercepte. En este caso será main
try:
    print f1(4)
except MyError as infos:
    print "type :%s, arguments : %s" % (type(infos),infos)

# un método puede enriquecer las excepciones que remite
def f4(x):
    try:
        return f5(x)
    except MyError as infos:
        # enriquecemos la excepción y luego la relanzamos
        raise MyError(infos,"exception dans f4")

def f5(y):
    try:
        return f6(y)
    except MyError as infos:
        # enriquecemos la excepción y luego la relanzamos
        raise MyError(infos, "exception dans f5")

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

#---------- main
try:
    print f4(4)
except MyError as infos:
    print "type :%s, arguments : %s" % (type(infos),infos)

Notas:

  • líneas 27-30, en la llamada main --> f1 --> f2 --> f3 (línea 28), 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 29;
  • líneas 55-58: en la llamada main --> f4 --> f5 --> f6 (línea 56), la excepción lanzada MyError por f6 se propagará hasta main. A continuación, será tratada por la cláusula except de la línea 29. En esta ocasión, al remontar por la cadena de métodos llamantes, la excepción MyError se enriquece con la información añadida por cada método remontado.

Los resultados en pantalla son los siguientes:

type :<class '__main__.MyError'>, arguments : exception dans f3
type :<class '__main__.MyError'>, arguments : (MyError(MyError('exception dans f6',), 'exception dans f5'), 'exception dans f4')