Capítulo 6. Excepciones y gestión de ficheros

En este capítulo se zambullirá en las excepciones, objetos de fichero, bucles for, y los módulos os y sys. Si ha usado excepciones en otros lenguaje de programación puede leer por encima la primera sección para hacerse con la sintaxis de Python. Pero asegúrese de prestar de nuevo sus cinco sentidos para la gestión de ficheros.

6.1. Gestión de excepciones

Como muchos otros lenguajes de programación, Python gestiona excepciones mediante bloques try...except.

nota
Python utiliza try...except para capturar las excepciones y raise para generarlas. Java y C++ usan try...catch para capturarlas, y throw para generarlas.

Las excepciones se las encuentra en todos lados en Python. Prácticamente cada módulo estándar de Python hace uso de ellas, y el propio Python las lanzará en muchas circunstancias diferentes. Ya las ha visto varias veces a lo largo de este libro.

En cada uno de estos casos estábamos jugando simplemente con el IDE de Python: ocurría un error, se mostraba la excepción (dependiendo del IDE, quizá en un tono de rojo intencionadamente irritante), y eso era todo. A esto se le denomina una excepción sin gestionar (unhandled). Cuando se lanzó la excepción, no había código explícito que lo advirtiese e hiciera algo al respecto, de manera que subió hasta la superficie y disparó el comportamiento por omisión de Python, que es mostrar algo de información para depuración y terminar la ejecución. En el IDE esto no es mucho problema, pero si ocurre mientras está ejecutándose un verdadero programa escrito en Python, todo el programa terminaría con un gran estrépito.

Una excepción no tiene por qué resultar en un completo desastre, sin embargo. Las excepciones, tras ser lanzadas, pueden ser controladas. Algunas veces ocurren excepciones debido a fallos en el programa (como acceder a una variable que no existe), pero a menudo sucede por algo que podemos anticipar. Si abrimos un fichero, puede ser que no exista. Si conectamos a una base de datos, puede que no esté disponible, o quizá no introdujimos las credenciales de seguridad correctas al acceder. Si sabe qué línea de código lanzará la excepción, podría gestionarla utilizando un bloque try...except.

Ejemplo 6.1. Apertura de un fichero inexistente

>>> fsock = open("/noexiste", "r")      1
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
IOError: [Errno 2] No such file or directory: '/noexiste'
>>> try:
...     fsock = open("/noexiste")       2
... except IOError:                     3
...     print "El fichero no existe, terminamos de forma elegante"
... print "Siempre se va a imprimir esta línea" 4
El fichero no existe, terminamos de forma elegante
Siempre se va a imprimir esta línea
1 Podemos intentar abrir un fichero para leerlo usando la función incorporada open (más sobre open en la próxima sección). Pero el fichero no existe, y esto provoca una excepción IOError. Como no hemos proporcionado una comprobación explícita en busca de excepciones IOError, Python se limita a imprimir algo de información de depuración sobre lo que sucedió, y luego se detiene.
2 Intentamos abrir de nuevo el mismo fichero inexistente, pero esta vez lo hacemos dentro de un bloque try...except.
3 Cuando el método open lanza una excepción IOError, estamos preparados para ello. La línea except IOError: captura la excepción y ejecuta su bloque de código, que en este caso simplemente imprime un mensaje de error más elegante.
4 Una vez controlada la excepción, el proceso continúa de forma normal en la primera línea tras el bloque try...except. Observe que esta línea se imprime siempre, haya o no ocurrido la excepción. Si realmente tuviera un fichero llamado noexiste en el directorio raíz, la llamada a open tendría éxito, se ignoraría la cláusula except, pero esta línea se ejecutaría de todas maneras.

Las excepciones pueden parecer poco amigables (después de todo, si no las captura, el programa entero se va al traste), pero considere la alternativa. ¿Preferiría tener un objeto de fichero inútil sobre un fichero no existente? Necesitaría comprobar su validez de todas maneras, y si se olvidase, en algún momento por debajo de esa línea el programa mostraría errores extraños que debería trazar en el código fuente. Estoy seguro de que lo ha experimentado alguna vez, y sabe que no es divertido. Con las excepciones, el error ocurre de inmediato y puede controlarlo de una manera estándar en la fuente del problema.

6.1.1. Uso de excepciones para otros propósitos

Hay muchos otros usos para las excepciones aparte de controlar verdaderas condiciones de error. Un uso común en la biblioteca estándar de Python es intentar importar un módulo, y comprobar si funcionó. Importar un módulo que no existe lanzará una excepción ImportError. Puede usar esto para definir varios niveles de funcionalidad basándose en qué módulos están disponibles en tiempo de ejecución, o para dar soporte a varias plataformas (donde esté separado el código específico de cada plataforma en varios módulos).

También puede definir sus propias excepciones creando una clase que herede de la clase incorporada Exception, para luego lanzarlas con la orden raise. Vea la sección de lecturas complementarias si le interesa hacer esto.

El próximo ejemplo demuestra el uso de una excepción para dar soporte a funcionalidad específica de una plataforma. Este código viene en getpass, un módulo accesorio para obtener la clave del usuario. Esto se hace de manera diferente en las plataformas UNIX, Windows y Mac OS, pero el código encapsula todas esas diferencias.

Ejemplo 6.2. Dar soporte a funcionalidad específica de una plataforma

  # Asociamos el nombre getpass a la función apropiada
  try:
      import termios, TERMIOS                     1
  except ImportError:
      try:
          import msvcrt                           2
      except ImportError:
          try:
              from EasyDialogs import AskPassword 3
          except ImportError:
              getpass = default_getpass           4
          else:                                   5
              getpass = AskPassword
      else:
          getpass = win_getpass
  else:
      getpass = unix_getpass
1 termios es un módulo específico de UNIX que proporciona control de la terminal a bajo nivel. Si no está disponible este módulo (porque no está en el sistema, o el sistema no da soporte), la importación falla y Python genera una ImportError, que podemos capturar.
2 OK, no tenemos termios, así que probemos con msvcrt, que es un módulo específico de Windows que propociona un API a muchas funciones útiles en los servicios de ejecución de Visual C++. Si falla esta importación, Python lanzará una ImportError, que capturaremos.
3 Si las dos primeras fallaron, podemos tratar de importar una función de EasyDialogs, un módulo específico de Mac OS que proporciona funciones para mostrar ventanas de diálogo de varios tipos. Una vez más, si falla esta operación, Python lanzará una ImportError, que capturaremos.
4 Ninguno de estos módulos específicos de la plataforma están disponibles (puede suceder, ya que Python ha sido adaptado a muchas plataformas), de manera que necesita acudir a una función de introducción de password por omisión (que está definida en otra parte del módulo getpass). Observe lo que estamos haciendo aquí: asignamos la función default_getpass a la variable getpass. Si lee la documentación oficial de getpass, le dirá que el módulo getpass define una función getpass. Lo hace asociando getpass a la función adecuada para su plataforma. Cuando llama a la función getpass, realmente está invocando una función específica de la plataforma en la que se ejecuta su código (simplemente llame a getpass, que siempre estará haciendo lo correcto).
5 Un bloque try...except puede tener una cláusula else, como la sentencia if. Si no se lanza una excepción durante el bloque try, al final se ejecuta la cláusula else. En este caso, eso significa que funcionó la importación from EasyDialogs import AskPassword, de manera que deberíamos asociar getpass a la función AskPassword. Cada uno de los otros bloques try...except tiene cláusulas else similares para asociar getpass a la función apropiada cuando se encuentre un import que funcione.

Lecturas complementarias sobre la gestión de excepciones