Universidad de Costa Rica | Escuela de Ingeniería Eléctrica IE-0117 Programación Bajo Plataformas Abiertas Manejo de errores y excepciones En un programa de cómputo es necesario manejar de forma adecuada los posibles errores que puedan surgir, con el fin de que el usuario experimente la menor cantidad posible de problemas. El programador debe ser capaz de reconocer los posibles problemas que puedan presentarse durante la ejecución del programa y tomar las acciones necesarias para que el error no produzca resultados incorrectos o provoque que la aplicación se cierre de manera abrupta. Además, el programador debe asegurarse de que el programa notifique al usuario, a través de mensajes de error, los problemas que se presenten. De esta manera, el usuario puede tomar las acciones necesarias para corregir la situación y utilizar el programa de forma exitosa. Los mensajes de error deben ser claros y descriptivos, para facilitar su comprensión por parte de usuarios sin experiencia. 1. Prácticas recomendadas para el manejo de errores 1.1. Verificación de la cantidad de argumentos de entrada Si el usuario no proporciona la cantidad correcta de argumentos en la línea de comandos, el programa no funcionará adecuadamente. Puede verificarse la cantidad de argumentos con una prueba simple al inicio de programa: import sys if len(sys.argv) != NUM_ARGS: # Imprime mensajes de error print >> sys.stderr, "Número de argumentos incorrecto" print >> sys.stderr, "Uso: %s a b c" % sys.argv[0] # Termina regresando un "1" al shell para indicar error sys.exit(1) ... 1.2. Pruebas sobre los argumentos Si se trata de convertir una cadena a otro tipo de datos y el contenido de la cadena no es el adecuado se producirá un error. Es posible usar funciones como isdigit() antes de la conversión para verificar que la cadena sea convertible. Pueden hacerse verificaciones avanzadas usando otras herramientas como las expresiones regulares1 . Las expresiones regulares permiten definir patrones abstractos (por ejemplo direcciones de correo o números de teléfono) que se buscarán dentro de una cadena. 1.3. Verificación de existencia de directorios y arivos Es conveniente verificar la existencia de rutas de archivos y directorios proporcionadas por el usuario antes de intentar accederlas. Para esto pueden usarse algunas funciones del módulo os.path: exists(): isdir(): isfile(): retorna True si la ruta existe, sea un archivo o directorio. retorna True si la ruta corresponde a un directorio que existe en el sistema. retorna True si la ruta corresponde a un archivo convencional en el sistema. 1 http://docs.python.org/library/re.html 1 IE-0117 Programación Bajo Plataformas Abiertas Manejo de errores y excepciones 1.4. Valores de retorno adecuados en las funciones En caso de que se de un error en una función, esta debe retornar un valor adecuado que pueda ser detectado por el programador que usa la función. Una buena práctica es retornar explícitamente None cuando se produce un error: def test_float(num): cont_puntos = 0 for char in num: if char == '.': cont_puntos += 1 elif not char.isdigit(): # Hay caracteres no numéricos return None if cont_puntos > 1: # Hay más de un punto return None else: # Si todo salió bien retorna número convertido return float(num) En este caso, la función test_float() retorna None si la cadena que se trata de convertir a float contiene caracteres no numéricos o más de un carácter punto. De esta forma es posible capturar el posible error al llamar a la función: a = raw_input("Ingrese un número: ") na = test_float(a) if not na: # test_float(a) retornó None, 'a' no era un número de punto flotante ... else: # Todo bien, puede continuar el programa ... 2. Excepciones Python, al igual que la mayoría de los lenguajes de programación orientados a objetos, provee un mecanismo para el manejo de errores muy eficiente: las excepciones. Una excepción es una condición que se dispara automáticamente cuando se produce un error. Si la excepción no se captura, el intérprete imprimirá un mensaje de error y cerrará el programa. Sin embargo, es posible capturar la excepción y mover el flujo del programa a una rutina especial para manejo de errores que se ejecutará sólo cuando se de el error. 2.1. Captura de excepciones Suponga que se solicita al usuario un valor, el cual será convertido a un número de punto flotante: a = raw_input("Ingrese un número: ") na = float(a) Si el usuario introduce un valor erróneo se producirá entonces una excepción que, si no es capturada, imprimirá el siguiente mensaje y cerrará el programa: Traceback (most recent call last): ... ValueError: invalid literal for float(): 12.4asb 2 IE-0117 Programación Bajo Plataformas Abiertas Manejo de errores y excepciones Es posible capturar la excepción usando la palabra clave except para manejar el error adecuadamente: try: a = raw_input("Ingrese un número: ") na = float(a) except ValueError: print >>sys.stderr, "El valor introducido no es un número" ... Cada vez que se produce una excepción se genera un objeto con un tipo determinado. El tipo de este objeto es el que indica la clase de excepción que se produce. En el ejemplo anterior, la excepción que se produce es del tipo ValueError, que se da cuando el valor que se pasa a una función u operador no es el correcto. El enunciado except indica que se capturará específicamente este tipo de excepción. Si se produjera una excepción de otro tipo, esta no sería manejada. Es posible capturar varios tipos de excepciones en un mismo enunciado except, usando una tupla: try: a = raw_input("Ingrese un número: ") na = float(a) except (ValueError, NameError, IOError): print >>sys.stderr, "Se produjo un error" ... También es posible usar varios bloques except, para procesar distintas excepciones por separado: try: a = raw_input("Ingrese un número: ") na = float(a) except ValueError: print >>sys.stderr, "El valor introducido no es un número" except IOError: print >>sys.stderr, "Se produjo un error de Entrada/Salida" ... Todos los tipos de datos de excepción tienen una clase base llamada Exception. Esto permite crear un bloque except que captura una excepción genérica: try: a = raw_input("Ingrese un número: ") na = float(a) except ValueError: print >>sys.stderr, "El valor introducido no es un número" except Exception: # Captura una excepción genérica print >>sys.stderr, "Se produjo un error" ... 3 IE-0117 Programación Bajo Plataformas Abiertas Manejo de errores y excepciones Además del bloque try... except, puede agregarse un bloque else, que define un bloque de código que se ejecutará si la excepción no se produce: try: a = raw_input("Ingrese un número: ") na = float(a) except ValueError: print >>sys.stderr, "El valor introducido no es un número" else: # No se produjo la excepción, puede usarse la variable na ... También existe el bloque finally, que permite definir código que será ejecutado siempre, sin importar si se produce o no la excepción. Este bloque es útil para realizar tareas de limpieza y liberación de recursos: try: datos = f.readlines() except IOError: print >> sys.stderr, "Error leyendo datos desde el archivo" finally: # Cierra el archivo, haya o no haya excepción f.close() 2.2. Excepciones comunes Algunos tipos de datos de excepción comunes son: ValueError: IOError: Se produce cuando se pasa un valor incorrecto a una función u operador. Error de entrada/salida (por ejemplo al leer datos de un archivo). KeyboardInterrupt: IndexError: KeyError: Ocurre cuando el usuario oprime las teclas Ctrl-c. Se produce cuando se trata de acceder a un elemento de una lista que no existe. Se da cuando se trata de acceder a un elemento de un diccionario con una llave que no existe. ZeroDivisionError: Se produce cuando se trata de dividir entre cero. TypeError: Error de tipo. Se produce cuando un operador o función recibe un dato con un tipo distinto al esperado. NameError: Ocurre cuando se trata de utilizar una variable, clase o función que no se ha definido. ImportError: Se da cuando se trata de importar un módulo que no existe. Los módulos, tanto los que forman parte de la biblioteca estándar como los provistos por terceras partes, pueden definir nuevas excepciones, que se adapten a las necesidades específicas de cada caso. 4 IE-0117 Programación Bajo Plataformas Abiertas Manejo de errores y excepciones 2.3. Acceso al objeto excepción Es posible tener acceso al objeto excepción durante la captura, usando un argumento extra en el enunciado except: try: a = raw_input("Ingrese un número: ") na = float(a) except ValueError, error: print >>sys.stderr, "El valor introducido no es un número" print >>sys.stderr, "Información adicional:", error ... Si se produce el error se imprimiría: El valor introducido no es un número Información adicional: invalid literal for float(): 12.4asb Algunas excepciones proporcionan información adicional a través de métodos o propiedades, que puede ser de valor en algunos casos. 2.4. Disparo de excepciones Es posible generar exepciones en un punto arbitrario de un programa. Esto es particularmente útil cuando se están creando funciones o módulos que se utilizarán luego en otras partes del programa. Por ejemplo, podría escribirse una función que utilice excepciones para asegurar que el número que recibe como argumento sea positivo def mi_funcion(a): import math if a < 0: raise ValueError("El número debe ser positivo") else: return math.sqrt(a) + 9.80 Al llamar a la función puede usarse entonces un bloque try... except: try: print mi_funcion(x) except ValueError, e: print >>sys.stderr, "Error:", e 2.5. Creación de excepciones personalizadas Es posible crear excepciones personalizadas utilizando nuevas clases, basadas en Exception: class MiExcepcion(Exception): def __init__(self, desc): self.desc = desc def __str__(self): return self.desc Una vez definida, la excepción podría dispararse como cualquier otra: 5 IE-0117 Programación Bajo Plataformas Abiertas Manejo de errores y excepciones raise MiExcepcion("Error, Error!") y luego podría ser capturada: try: # Código que podría provocar errores ... except MiExcepcion, e: # Código para manejar el error ... 6