Nuestra primera excepción

Excepciones en Python I: Control básico de excepciones

Hay situaciones en las que un programa se comporta de manera inesperada, incorrecta o, directamente interrumpe su ejecución de forma incontrolada. Tratar esas situaciones es el día a día de todos los que jugamos, profesional o personalmente, con la programación.

Muy por delante de saber escribir programas en un lenguaje de programación, el primer paso para convertirte en un buen programador es pensar que las cosas fallan y que hay que hacer todo lo posible para controlarlas si eso ocurre. La experiencia me ha demostrado que, si alguien se considera un buen programador, de todo el tiempo que invierta en un programa la mayoría se dedicará en tratar situaciones inesperadas para que el programa funcione bien en la mayoría de las circunstancias. Este concepto es esencial: un programa que funciona bien, funciona bien incluso cuando algo falla. Es más: debe hacerlo. 

En este artículo vamos a iniciarnos en el control de este tipo de situaciones cuando programamos con el lenguaje Python, de una forma un tanto básica pero que será de mucho provecho en los artículos que vienen a continuación en PItando.

En Python se distinguen dos tipos de problemas: errores y excepciones en el funcionamiento normal de un programa.

Errores

La documentación de Python define los errores como aquellos fallos humanos que hacen que un programa no cumpla su función. Ya sea porque el programa no funciona en absoluto (ni por un momento), o bien porque funciona… mal. Cuando me refiero a ellos como humanos es porque se producen durante el diseño y la construcción del programa, no es algo que entre dentro de la idea original de lo que debe hacer. Destacan, por ser los más obvios, los errores de sintaxis.

Por ejemplo, el siguiente código tiene un error sintáctico que lo hace inviable:

cadenas = ["piedra", "palo", "hoja"]
for cadena in cadenas print (cadena)

El error está en que falta un “:” entre el conjunto a recorrer por el bucle y la sentencia a repetir. La sentencia errónea si quiera se ejecuta, ni por un momento, y el programa termina porque no tiene sentido continuar.

Por otro lado, el siguiente programa tiene un error, también de sintaxis, que hace que el programa no haga lo que se desea (pruébalo en IDLE):

cadenas = ["palo", "piedra", "hoja"]
for cadena in cadenas:
    print(cadena)
    print("Se han terminado las cadenas")

Este ejemplo es muy sencillo. Obviamente y con sólo dar un vistazo al código, está claro que el programador quería recorrer las palabras de una lista, imprimirlas todas y terminar el ejercicio con un mensaje que indicase al usuario que había terminado. Sin embargo, un error de sintaxis como el de este caso, que es de sangrado (poner un tabulador donde no es), hace que el programa funcione, pero mal. Parece un ejemplo absurdo, pero ilustra bien problemas más complejos, como los que pueden surgir al ejecutar por primera vez un programa de más de 100 líneas, con bucles anidados y condiciones complejas.

Tener errores de sintaxis en el código puede tener dos efectos: que la escritura de un programa sea algo tedioso porque fallamos con la sintaxis y tenemos que editarlo varias veces antes de conseguir ejecutarlo, o que se produzcan comportamientos no esperados o erráticos, como en el segundo ejemplo. Evitar errores como el primero (que hace que el programa no se ejecute en absoluto) es algo que se consigue con la práctica, a base de conocer muy de cerca la sintaxis de Python. Evitar errores como el que vimos de sangrado, también. Sin embargo, como el programa va a ejecutarse (mal) a fin de cuentas, no siempre se pueden ver todos los errores antes de ejecutar el programa. Para evitar sorpresas eso es extremadamente importante probar bien el programa antes de darlo por terminado.

El apartado de probar los programas es tan amplio que ocupará varios artículos de este blog en lo sucesivo. Quédate con el mensaje de momento, y en lo sucesivo iré dando claves en ese proceso, medida que lo que hagamos en PItando nos lo vaya pidiendo.

Por suerte, la sintaxis de Python es sencilla, con lo que es fácil conseguir dominarla pronto.

Excepciones. Cómo mantenerlas bajo control.

Las excepciones son harina de otro costal. Son comportamientos anómalos que se producen porque las cosas fallan. Así de simple: todo falla. Porque un fichero de texto está corrupto. Porque el disco duro tiene un sector defectuoso. Porque el usuario interrumpe de forma abrupta la ejecución del programa. Porque el usuario introduce una letra cuando tu programa está esperando un número. Porque hay un cable flojo en un prototipo electrónico. Tu programa puede estar perfectamente escrito, y que algo pase: porque las cosas pasan, y si algo puede pasar, en algún momento ten por seguro que pasará.

Una vez asumido que, hagas lo que hagas, siempre llegará un momento en el que algo falle, llega el momento de ver cómo podemos actuar si eso ocurre. Como en la vida real, cuando algo falla está en nuestra responsabilidad evitar que las cosas se salgan de madre, y para eso lo que tenemos que hacer es controlar estas situaciones cuando se producen, teniendo las excepciones bajo control.

Vamos a hacer unas pruebas y explicar lo básico para poder empezar a aplicar estas técnicas de manejo de excepciones en nuestros programas, de ahora en adelante.

Como primer ejemplo, vamos a ver qué ocurre cuando itentamos dividir una letra por un número diferente de cero:

"C" / 3

 El resultado que verás es el siguiente:

Nuestra primera excepción
Nuestra primera excepción

Lo que nos dice el intérprete de Python es, básicamente, que la combinación de operandos que hemos intentado usar con el operador “división” no está soportado. En detalle, lo que ocurre es que el operando de la división intenta dividir los dos operandos, pero al recibir una letra no puede ofrecer un resultado correcto. Por ello, termina elevando (o lanzando) una excepción de tipo TypeError directamente al intérprete: lo puedes ver al inicio de la última línea de texto rojo. Toda vez que el intérprete de Python reciba una excepción va a detener el programa, es decir: no va a ejecutar nada que esté escrito después de la línea que provoca la excepción.

Controlar las excepciones significa dos cosas: (1) hacer algo para que el intérprete no reciba una excepción, y (2): hacer que el programa reaccione a la excepción de forma coherente. Es decir, si puede continuar, que continúe. Si no puede continuar, que termine su ejecución de forma ordenada: cerrando los ficheros que haya abiertos, emitiendo al sistema operativo códigos de salida en consecuencia, informando al usuario del problema, etcétera.

Para controlar las excepciones, Python nos ofrece la sentencia try... except... else... finally; parece compleja pero no lo es tanto, es más: es de las más intuitivas. Vamos a verla en detalle con un ejemplo.

try:
	res = x / y
except (TypeError):
	print ("Se ha producido una excepción")
else:
	print ("el resultado es ", res)
finally:
	print ("Esta sentencia se ejecuta en cualquier caso")

 El código del recuadro superior puede leerse de la siguiente forma:

Intenta:
    dividir x entre y
Para las excepciones de tipo "TypeError":
    imprime un mensaje
Si no se ha producido excepción alguna:
    imprime el resultado
En cualquier caso:
    imprime otro mensaje

Prueba a ejecutar el código de arriba con y = 3  y asignando a x valores arbitrarios, como True, 3, "y". Verás que, o bien se imprime el mensaje “Se ha producido una excepción” y “Esta sentencia se ejecuta en cualquier caso”, o bien se imprime un número resultado de la división y “Esta sentencia se ejecuta en cualquier caso”.

Resultado en caso de excepción.
Resultado en caso de excepción.
Funcionamiento del ejemplo en el caso en el que no hay ninguna excepción
Funcionamiento del ejemplo en el caso en el que no hay ninguna excepción

Ahora prueba a asignar x = 3 e y = 0:

Funcionamiento del ejemplo en el caso en el que hay una excepción no controlada
Funcionamiento del ejemplo en el caso en el que hay una excepción no controlada

Como ves, el código en el bloquefinally siempre se ejecuta: vaya el resultado bien con el flujo de ejecución progresando por la salida else , se capture una excepción mediante except , o se produzca algo totalmente inesperado y no capturado. Un hecho especialmente significativo es que finally  se ejecuta antes de que se eleve la excepción: el intérprete percibe la excepción sólo cuando el bloque contenido bajo finally  ha terminado. Así pues, el valor clave de finally  reside en que, pase lo que pase, podremos realizar tareas de limpieza y seguridad como podría ser intentar cerrar el fichero, guardar datos del usuario…

Vamos ahora a terminar el ejemplo capturando la excepción recién descubierta, que como se ve en el mensaje de la última imagen, es ZeroDivisionError: prueba el código siguiente con los valores que quieras en las dos variables.

try:
	res= x / y
except (TypeError):
	print ("Se ha producido una excepción")
except (ZeroDivisionError):
	print ("No se puede dividir entre cero en Python")
else:
	print ("el resultado es ", res)
finally:
	print ("Esta sentencia se ejecuta en cualquier caso")

El programa superior también se puede escribir de una forma más compacta aunque, en caso de error, mostrará menos información al usuario ya que no distingue el tipo de excepción en el mensaje:

try:
	res= x / y
except (TypeError, ZeroDivisionError):
	print ("Excepción")
else:
	print ("el resultado es ", res)
finally:
	print ("Esta sentencia se ejecuta en cualquier caso")

Puedes volverlo a probar si no defines una de las dos variables (en lugar de x/y, escribe t/y y ejecútalo sin asignar valor a t), tratar de identificar el error que se emitirá y escribir el bloque de control.

Conclusiones

Con lo que hemos visto en este artículo podemos coincidir en que es importante:

  1. Escribir bajo try el código que es susceptible de provocar una excepción. Este tipo de código es muy amplio y normalmente se identifica consultando la documentación de una función escribiendo help(funcion), aunque tristemente la documentación de Python disponible a través de IDLE está en inglés. Ejemplos fácilmente identificables hasta ahora son los siguientes:
    1. Operaciones aritméticas sobre variables
    2. Operaciones con ficheros (apertura, escritura, lectura)
    3. Que el usuario pulse Control y C, a la vez, cuando ejecutamos un programa por línea de comandos.
  2. Las excepciones se capturan en bloques except
    1. Si el tratamiento de varios tipos de excepciones es el mismo, se pueden pasar todas al mismo bloque: except(TypeError, KeyboardInterrupt)
    2. Si no, se pone uno detrás de otro:
      try:
          ...
      except (TypeError):
          # Gestionar tipo incorrecto
          ...
      except (KeyboardInterrupt):
          # Gestionar interrrupción de usuario
          ...
      
  3. Todo el código que dependa del correcto resultado del bloque bajo try, lo escribimos bajo else. De esta forma, sólo ejecutará cuando no se produzca excepción alguna. Es una forma de escribir buen código Python, ya que de esa forma no capturaremos ninguna excepción que no nos corresponda tratar a nosotros. En otros artículos de excepciones y control de las mismas, más avanzados, incidiré en este aspecto; de momento piensa que, igual que en la vida real: cada uno es responsable de gestionar los problemas que él mismo se arriesga a provocar, pero no arreglar los de los demás 😀
  4. Las sentencias de limpieza y liberación de recursos van dentro de finally: ya que siempre se ejecuta, es el mejor lugar para hacer cosas como, por ejemplo, liberar memoria, cerrar ficheros o dejar el sistema de GPIO en un estado de reposo con la función GPIO.cleanup(). finallyes muy valioso, y mi recomendación es que siempre escribas un bloque finally.
Ejemplo de la ayuda integrada en IDLE. Como ves, está en inglés, pero si buscas la palabra "Raise", que significa "eleva" (elevar una excepción), es fácil localizar la información de interés.
Ejemplo de la ayuda integrada en IDLE. Como ves, está en inglés, pero si buscas la palabra “Raise”, que significa “eleva” (elevar una excepción), es fácil localizar la información de interés.

Espero que haya sido interesante. Podéis dejarme cualquier comentario en esta misma entrada, o enviándome cualquier comentario a tavés del formulario de contacto y la dirección de correo que allí os indico. Recordad también que PItando está tanto en twitter, como en Facebook, y en Google+ también podéis seguir mis publicaciones, incluyendo las del podcast (iTunesiVooxRSS).

Un comentario en “Excepciones en Python I: Control básico de excepciones

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *