Subsecciones


8. Errores y excepciones

Hasta ahora no habíamos más que mencionado los mensajes de error, pero si has probado los ejemplos puede que hayas visto algunos. Hay (al menos) dos tipos de errores diferenciables: los errores de sintaxis y las excepciones.


8.1 Errores de sintaxis

Los errores de sintaxis son la clase de queja más común del intérprete cuando todavía estás aprendiendo Python:

>>> while True print 'Hola mundo'
  File "<stdin>", line 1, in ?
    while True print 'Hola mundo'
                ^
SyntaxError: invalid syntax

El intérprete sintáctico repite la línea ofensiva y presenta una `flechita' que apunta al primer punto en que se ha detectado el error. La causa del error está (o al menos se ha detectado) en el símbolo anterior a la flecha: En este ejemplo, el error se detecta en la palabra clave print, porque faltan los dos puntos (":") antes de ésta. Se muestran el nombre del fichero y el número de línea para que sepas dónde buscar, si la entrada venía de un fichero.


8.2 Excepciones

Aun cuando una sentencia o expresión sea sintácticamente correcta, puede causar un error al intentar ejecutarla. Los errores que se detectan en la ejecución se llaman excepciones y no son mortales de necesidad, pronto va a aprender a gestionarlos desde programas en Python. Las excepciones no capturadas causan mensajes de error como el siguiente:

>>> 10 * (1/0)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero
>>> 4 + fiambre*3
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: name 'fiambre' is not defined
>>> '2' + 2
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects

La última línea del mensaje de error indica qué ha pasado. Las excepciones pueden ser de diversos tipos, que se presentan como parte del mensaje: los tipos del ejemplo son ZeroDivisionError, NameError y TypeError. La cadena presentada como tipo de excepción es el nombre de la excepción interna que ha ocurrido. Esto es aplicable a todas las excepciones internas, pero no es necesariamente cierto para las excepciones definidas por el usuario (sin embargo, es una útil convención). Los nombres de excepciones estándar son identificadores internos (no palabras reservadas).

El resto de la línea proporciona detalles que dependen del tipo de excepción y de qué la causó.

La parte anterior del mensaje de error muestra el contexto donde ocurrió la excepción, en forma de trazado de la pila. En general, contiene un trazado que muestra las líneas de código fuente, aunque no mostrará las líneas leídas de la entrada estándar.

La Referencia de las bibliotecas enumera las excepciones internas y sus respectivos significados.


8.3 Gestión de excepciones

Es posible escribir programas que gestionen ciertas excepciones. Mira el siguiente ejemplo, que pide datos al usuario hasta que se introduce un entero válido, pero permite al usuario interrumpir la ejecución del programa (con Control-C u otra adecuada para el sistema operativo en cuestion); Observa que una interrupción generada por el usuario se señaliza haciendo saltar la excepcion KeyboardInterrupt.

>>> while True:
...     try:
...         x = int(raw_input("Introduce un número: "))
...         break
...     except ValueError:
...         print "¡Huy! No es un número. Prueba de nuevo..."
...

La sentencia try funciona de la siguiente manera:

Una sentencia try puede contener más de una cláusula except, para capturar diferentes excepciones. Nunca se ejecuta más de un gestor para una sola excepción. Los gestores sólo capturan excepciones que saltan en la cláusula try correspondiente, no en otros gestores de la misma sentencia try. Una cláusula try puede capturar más de una excepción, nombrándolas dentro de una lista:

... except (RuntimeError, TypeError, NameError):
...     pass

La última cláusula except puede no nombrar ninguna excepción, en cuyo caso hace de comodín y captura cualquier excepción. Se debe utilizar esto con mucha precaución, pues es muy fácil enmascarar un error de programación real de este modo. También se puede utilizar para mostrar un mensaje de error y relanzar la excepción (permitiendo de este modo que uno de los llamantes gestione la excepción a su vez):

import sys

try:
    f = open('mifichero.txt')
    s = f.readline()
    i = int(s.strip())
except IOError, (errno, strerror):
    print "Error de E/S(%s): %s" % (errno, strerror)
except ValueError:
    print "No ha sido posible covertir los datos a entero."
except:
    print "Error no contemplado:", sys.exc_info()[0]
    raise

La sentencia try ... except tiene una cláusula else opcional, que aparece tras las cláusulas except. Se utiliza para colocar código que se ejecuta si la cláusula try no hace saltar ninguna excepción. Por ejemplo:

for arg in sys.argv[1:]:
    try:
        f = open(arg, 'r')
    except IOError:
        print 'no se puede abrir', arg
    else:
        print arg, 'contiene', len(f.readlines()), 'líneas'
        f.close()

El uso de la cláusula else es mejor que añadir código adicional a la cláusula try porque evita que se capture accidentalmente una excepción que no fue lanzada por el código protegido por la sentencia try ... except.

Cuando salta una excepción, puede tener un valor asociado también conocido como el/los argumento/s de la excepción. Que el argumento aparezca o no y su tipo dependen del tipo de excepción.

La cláusula except puede especificar una variable tras el nombre de la excepción o lista de excepciones. La variable se asocia a una instancia de excepción y se almacenan los argumentos en instance.args. Por motivos prácticos, la instancia de la excepción define __getitem__ y __str__ para que los argumentos se puedan acceder o presentar sin tener que hacer referencia a .args.

>>> try:
...    raise Exception('magro', 'huevos')
... except Exception, inst:
...    print type(inst)     # la instancia de la excepción
...    print inst.args      # los argumentos guardados en .args
...    print inst           # __str__ permite presentar los args directamente
...    x, y = inst          # __getitem__ permite desempaquetar los args "
...    print 'x =', x
...    print 'y =', y
...
<type 'instance'>
('magro', 'huevos')
('magro', 'huevos')
x = magro
y = huevos

Si una excepción no capturada tiene argumento, se muestra como última parte (detalle) del mensaje de error.

Los gestores de excepciones no las gestionan sólo si saltan inmediatamente en la cláusula try, también si saltan en funciones llamadas (directa o indirectamente) dentro de la cláusula try. Por ejemplo:

>>> def esto_casca():
...     x = 1/0
... 
>>> try:
...     esto_casca()
... except ZeroDivisionError, detalle:
...     print 'Gestión de errores:', detalle
... 
Gestión de errores: integer division or modulo by zero


8.4 Hacer saltar excepciones

La sentencia raise, hacer saltar, permite que el programador fuerce la aparición de una excepción. Por ejemplo:

>>> raise NameError, 'MuyBuenas'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: MuyBuenas

El primer argumento para raise indica la excepción que debe saltar. El segundo argumento, opcional, especifica el argumento de la excepción. De manera alternativa, se puede escribir lo anterior como raise NameError('MuyBuenas'). Cualquiera de las dos formas funciona perfectamente, pero parece haber una preferencia estilística creciente por la segunda.

Si necesitas determinar si se lanzó una excepción pero no tienes intención de gestionarla, una manera más sencilla de la sentencia raise permite relanzar la excepción:

>>> try:
...     raise NameError, 'MuyBuenas'
... except NameError:
...     print '¡Ha saltado una excepción!'
...     raise
...
¡Ha saltado una excepción!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
  NameError: MuyBuenas


8.5 Excepciones definidas por el usuario

Los programas pueden nombrar sus propias excepciones creando una nueva clase de excepción. Suele ser adecuado que las excepciones deriven de la clase Exception, directa o indirectamente. Por ejemplo:

>>> class MiError(Exception):
...     def __init__(self, valor):
...         self.valor = valor
...     def __str__(self):
...         return `repr(self.valor)
... 

>>> try:
...     raise raise MiError(2*2)
... except MiError, e:
...     print 'Ha saltado mi excepción, valor:', e.valor
... 
Ha saltado mi excepción, valor: 4
>>> raise MiError, '¡Huy!'
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MiError: '¡Huy!'

En este ejemplo, el __init__ predeterminado de Exception se ha redefinido. El nuevo comportamiento simplemente crea el atributo value. Esto sustituye al comportamiento predefinido de crear el atributo args.

Se pueden definir clases de excepción que hagan cualquier cosa que pudiera hacer cualquier otra clase, pero suelen hacerse simples, limitándose a ofrecer atributos que permiten a los gestores del error extraer información sobre el error. Cuando se crea un módulo que puede hacer saltar excepciones, es una práctica común crear una clase base para todas las excepciones definidas en el módulo y crear subclases específicas para las diferentes condiciones de error:

class Error(Exception):
    """Clase base para todas las excepciones del módulo."""
    pass

class EntradaError(Error):
    """Excepción lanzada para errores de entrada.

    Atributos:
        expresion -- expresión de entrada en la que ocurrió el error
        mensaje -- explicación del error
    """

    def __init__(self, expresion, mensaje):
        self.expresion = expresion
        self.mensaje = mensaje

class TransicionError(Error):
    """Salta cuando una operación intenta una transición de estado
    no permitida.
    
    Atributos:
        actual -- estado inicial de la transición
	nuevo -- nuevo estado deseado
        mensaje -- explicación de por qué no se permite la transición
    """

    def __init__(self, actual, nuevo, mensaje):
        self.actual = actual
        self.nuevo = nuevo
        self.mensaje = mensaje

La mayoría de las excepciones se nombran terminando en ``Error,'' tal como las excepciones estándar.

Muchos módulos estándar definen sus propias excepciones para informar de errores que pueden darse en las funciones que definen. Hay más información sobre clases en el capítulo 9, ``Clases.''


8.6 Definir acciones de limpieza

La sentencia try tiene otra cláusula opcional cuya intención es definir acciones de limpieza que se han de ejecutar en cualquier circunstancia. Por ejemplo:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print '¡Adiós, mundo cruel!'
... 
¡Adiós, mundo cruel!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
KeyboardInterrupt

La cláusula finally (finalmente) se ejecuta tanto si ha saltado una excepción dentro de la cláusula try como si no. Si ocurre una excepción, vuelve a saltar tras la ejecución de la cláusula finally. También se ejecuta la cláusula finally ``a la salida'' si se abandona la sentencia try mediante break o return.

El código de la cláusula finally es útil para liberar recursos externos (como ficheros o conexiones de red), independientemente de si el uso del recurso fue satisfactorio o no.

Una sentencia try debe tener una o más cláusulas except o una cláusula finally, pero no las dos (ya que no estaría claro cuál de las cláusulas ejecutar).

Consultar en Acerca de este documento... información para sugerir cambios.