Del libro Learning Python, de Mark Lutz. Ejercicios de la parte VII, acerca de las excepciones en Python.
El ejercicio 1, aunque están resueltos en el propio libro, pongo mi solución por aquí y la comento:
- Enunciado: Escribir una función llamada oops que lanza una excepción IndexError. Después, escribir otra función que llame a oops dentro de una estructura try/except para capturar la excepción. ¿Qué ocurre si oops lanzara la excepción KeyError? ¿De dónde vienen los nombres KeyError e IndexError? (Pista: recuerda que todos los identificadores no calificados vienen de uno de los cuatro ámbitos, según la regla LEGB)
- Mi solución:
def oops(excepcion):
raise excepciondef caller():
try:
oops(KeyError)
except IndexError:
print("Received an IndexError exception")
except KeyError:
print("Received an KeyError exception")if __name__=="__main__":
caller()
- La voy a ir comentando:
raise excepcion
Define una función llamada oops que recibe un único argumento referenciado por el identificador excepcion. Notar que no se define de qué tipo debe ser el argumento, si lo que hiciera nuestra función funcionara igual con enteros que con floats no tendríamos que programar dos funciones diferentes, nos valdría con la misma.
En el cuerpo de la función, raise lanza una excepción. De esta manera, se consigue que se deje de ejecutar la función oops nada más hacer el raise, y la función que llamó a ésta reciba la excepción.
try:
oops(KeyError)
except IndexError:
print("Received an IndexError exception")
except KeyError:
print("Received an KeyError exception")
Define la función caller, que no recibe argumentos. En esta función hay un bloque try/except. En este tipo de bloques se ejecuta el cuerpo del try, y si durante la ejecución se recibe alguna excepción, se comprueba con los bloques except que haya (al menos uno).
En este caso el primer bloque except comprueba si se recibió la excepción IndexError, en cuyo caso ejecuta la sentencia print() que se ve ahí. El print() va con paréntesis porque es la sintaxis exigida por Python 3000 (recién salido del horno), y soportada por versiones anteriores. Hasta ahora había sido habitual verlo como está pero sin esos paréntesis.
Esta función sencillamente llama a la función oops(), y le pasa como parámetro una excepción KeyError. Esa función lanzará entonces una excepción KeyError, que será recibida por el except KeyError de caller() (dado que la llamada a oops() se hizo dentro del try y que existe un except que captura esa excepción). Finalmente imprimirá el mensaje correspondiente según la excepción capturada.
caller()
__name__ es una variable built-in que indica el nombre del módulo que se está ejecutando. Si se trata del programa principal, tiene el valor ‘__main__’. Este código es habitual verlo en módulos, donde suele tener la función de ejecutar una serie de test sobre el propio módulo cuando es ejecutado en vez de cuando es incluido en otro script.
Por otra parte, ya contestando a la última pregunta, KeyError e IndexError son dos identificadores de sendas clases derivadas de la clase Exception. Tanto Exception, como sus derivadas KeyError e IndexError están alojadas en el built-in, es decir, que vienen de por sí en el lenguaje, igual que viene el tipo lista o la función print().
La regla LEGB hace referencia a Local, Enclosing function locals, Global y Built-in. Es decir, que python busca los identificadores de las variables en esos ámbitos, por ese orden: primero en variables locales (los argumentos también son variables locales) dentro de la función, luego en variables locales de funciones dentro de las que está definida la función actual, luego a nivel global, y por último, si no lo encontró en ningún nivel anterior, busca en el built-in.
Así, sí hay una variable global foo y otra local del mismo nombre e una función, al hacerle referencia desde la función, se hará referencia a su variable local, y no a la global.
Nota: la identación de código en WordPress la he hecho con el plugin Code-snippet. Y no es recomendable usar el modo visual con él, ya que se cepilla los espacios/tabulaciones.