Página inmutable History Adjuntos

Cursos/Principiantes/Parte7

Nombre de la página: Cursos / Principiantes / Parte7



Excepciones y técnicas de manejo de errores

A estas alturas del curso muy probablemente ya debieron de haber sido victimas frecuentes de mensajes como:

   1 SyntaxError: invalid syntax

   1 ZeroDivisionError: integer division or modulo by zero

   1 TypeError: unsupported operand type(s) for +: 'int' and 'str'

   1 SyntaxError: Non-ASCII character '

Entre muchas otras variedades de errores comunes, esos mensajes son una representación de una excepción, en esta parte vamos a ver como lidiar con estos además de entender su razón de existir y obtener el mayor provecho posible de las funcionalidades que nos ofrecen.

Sobre el manejo de errores

Todo programa que sea propiamente escrito debe de tener una estrategia definida de manejo y prevención de errores, verificar los argumentos a sus funciones, prevenir efectos secundarios indeseados entre otra muchas otras cosas.

A groso modo existen dos tipos de condiciones que pueden tener al estar pensando en como lidiamos con condiciones excepcionales, estas pueden ser fatales o no-fatales donde cada una de esos tipos de condiciones pueden ser esperadas o inesperadas.

Algunas veces realmente se espera que el programa termine lo más pronto posible, como por ejemplo que recibió argumentos erróneos para el programa, otras veces la condición que no se considera optima para la funcionalidad deseada se puede corregir ahí mismo y es una apuesta bastante segura que existen condiciones inesperadas, las cuales muy probablemente serán fatales y terminaran la ejecución del programa ó en el caso de que sean no-fatales muy probablemente tendrán efectos secundarios indeseados y casi siempre terminan como errores difíciles de corregir o dicho de otra forma bugs complicados de debuggear dependiendo de la organización y estructura del código así como la posibilidad de replicar las condiciones excepcionales.

Cabe destacar que la buena selección de estructuras de datos y la organización del código representan la barrera número uno para evitarnos un montón de problemas a futuro, en el casó que se tenga en mente mantener el código que se escribe en el futuro cercanos.

Estrategias para el manejo de errores

Desde una perspectiva simple existen dos "escuelas" del manejo de errores estás son look before you leap (LBYL) / mirar antes de saltar y easier to ask for forgiveness than permission (EAFP) / más fácil pedir perdón que permiso, en general la comunidad de python tiende a la segunda ya que el lenguaje esta diseñado para facilitar la forma de EAFP, lo cual casi siempre requiere un buen soporte al manejo de excepciones, esto no quiere decir que nunca de debe de usar LBYL, siempre es buena idea en casos donde se requiere especial cautela con las condiciones excepcionales, evitar tener efectos secundarios indeseados o que simplemente aporte mayor claridad al código.

LBYL

La estrategia look before you leap se basa en prevenir condiciones excepcionales esperadas, como por ejemplo:

   1 # name proviene de algun lugar misterioso
   2 if not isinstance(name, basestring):
   3    name = "S/N"
   4 print "Mi nombre es " + name 

o

   1 num = sys.argv[1]
   2 if num.isdigit():
   3    num = int(num)
   4 else:
   5    print "El argumento uno solo puede tener numeros"
   6    sys.exit(-1) # termina el programa

En los dos casos primero validamos que los argumentos sean los esperados luego operamos con los mismos, en el ejemplo dos, verificamos que el argumento número uno sea un número, dado que siempre se reciben como cadena, primero verificamos que la cadena contenga solo caracteres numéricos luego procedemos a convertirlo a un entero, dado que esta dentro del bloque del if garantizando que no va a ocurrir problema alguno al intentar hacer la conversión.

EAFP

La estrategia de EAFP se basa en aportar la mayor cantidad de código que refiere a la funcionalidad de forma continua y dejar los casos extraños en bloques aparte, en espera de la excepción en particular o un bloque que captura todos los tipos de errores, para que EAFP pueda existir es necesaria la construcción de excepciones, con bloques de try/exceptpara hacer una analogía a los ejemplos anteriores esta sería otra forma de lidiar con los argumentos que se esperan:

   1 try:
   2     print "Mi nombre es " + name 
   3 except TypeError: # perdon!! :{ 
   4     print "Mi nombre es S/N"

   1 try:
   2     num = int(sys.argv[1])
   3 except ValueError: # perdon, fue sin querer! :{ 
   4     print "El argumento uno solo puede tener numeros"
   5     sys.exit(-1) # termina el programa

try/except

La construcción básica para el manejo de excepciones es el bloque try/except donde tiene la siguiente estructura:

   1 try:
   2    # bloque de codigo con funcion primaria
   3 except TIPO_DE_EXCEPTION as ERR:
   4    # bloque de codigo con funcion para recuperar
   5    # la excepcion, donde se puede hacer uso de la
   6    # variable ERR

por ejemplo

   1 try:
   2     print "PyMTY " + {}
   3 except TypeError as err:
   4     print("Se capturo el error: %s" % (err,))
   5     print("No puedes concatenar cadenas y diccionarios!")

Levantando excepciones

Hasta el momento hemos visto como capturar excepciones, pero nunca se ha mencionado como nosotros mismo podemos levantar nuestras propias excepciones, para eso se ocupa la palabra reservada raise seguido de un objeto que herede Exception (de hecho puede heredar de BaseExeption, pero para fines prácticos Exception) o alguna otra excepción más especifica que se relacione, como TypeError o NotImplementedError.

   1 try:
   2     print "PyMTY " + {}
   3 except TypeError as err:
   4     print("Se capturo el error: %s" % (err,))
   5     raise Exception("No puedes concatenar cadenas y diccionarios!")

try/except...

El bloque try/except puede tener cualquier cantidad de excepts, que se evalúan de arriba hacia abajo, dejando las excepciones mas precisas cercas del bloque try y las mas genéricas al final, por ejemplo:

   1 class XError(TypeError):
   2     """
   3     Error especifico de X
   4     """
   5 try:
   6    raise XError()
   7 except TypeError:
   8     print "Se capturo TypeError"
   9 except XError:
  10     print "Se capturo XError"

Imprime "Se capturo TypeError", dado que se evalúa primero y la clase XError hereda de la clase TypeError, en cambio de esta forma:

   1 class XError(TypeError):
   2     """
   3     Error especifico de X
   4     """
   5 try:
   6    raise XError()
   7 except XError:
   8     print "Se capturo XError"
   9 except TypeError:
  10     print "Se capturo TypeError"

Imprime "Se capturo XError".

try/except.../else

La siguiente forma casi completa del bloque try/except agrega un bloque de código que se ejecuta solamente si no sucedió ninguna excepción en el bloque try, este se delimita con la palabra reservada else, solamente se puede tener un solo bloque else para cada bloque de excepciones, donde se requiere de por lo menos esperar una excepción con except.

Por ejemplo:

   1 try:
   2    print("pymty")
   3 except OSError as e:
   4    print("Error en el sistema operativo!")
   5 else:
   6    print "Todo normal..."

Imprime:

   pymty
   Todo normal...

try/except.../else/finally

Ahora si vamos a conocer el bloque en toda plenitud con el ultimo fragmento que tiene la palabra reservada de finally como denominador se ejecuta siempre sin importar si ocurrió o no una excepción en el bloque de try, esto es aún y tomando en cuenta que dentro del bloque try se use return para regresar un valor dentro de una función, primero ejecuta finally, luego regresa de la función en caso de que se use return.

   1 try:
   2     1 / 0
   3 except ZeroDivisionError:
   4     print "Con que dividiendo por cero..."
   5 except Exception:
   6     print "Error generico"
   7 else:
   8     print "Todo muy bien"
   9 finally:
  10     print "Limpiando memoria, cerrando locks y cosas asi."

ese ejemplo resulta en:

    Con que dividiendo por cero...
    Limpiando memoria, cerrando locks y cosas asi.

Conclusión

La construcción con bloques de try/except no es la unica forma de realizar el manejo de errores, pero si una muy versátil que se adecua bien al modelo de clases en python, dicho lo anterior no se recomienda ser completamente religiosos con esa aproximación (EAFP) y considerar cuando sería bueno combinarlo con LBYL.

Espero que luego de este pequeño viaje en el mundo de los errores sirva de un buen punto de partida para investigar muchas otras posibilidades de como usar las excepciones, por ejemplo como forma de comunicación no lineal a través de tus procesos; entre otros múltiples usos.