Mariano Rivera
Es un evento, probablemente catastrófico, durante la ejecución de un programa debido situaciones ajenas al propósito del algoritmo.
Asuma que nos piden implementar un programa para calcular el factorial usado la fórmula
con .
def factorial(n):
if n==0:
return 1
return n*factorial(n-1)
F = factorial(5)
print(F)
120
Ahora intentemos
F = factorial(-1)
Esto genera un error de ejecución, porque nuestro algoritmo no esta diseñado para calcular factoriales de números negativos. La razón es que los factoriales de números negativos no estan definidos.
¿Que debemos hacer? ¿Como modificar nuestra función factorial para este caso?
Lo que no debemos hacer es regresar un valor y dejar a quién llama a nuestra función la responsabilidad de interpretarlo: Debemos generar una excepción.
El manejo de exepciones en Python usa los estatutos (statements):
Aunque generalmente, dado que else y finally son opcionales, es común encontrar solo la combinación:
Significado:
try, bloque con las instrucciones que pueden generar la condición de excepción. Se usa rise para lanzar la excepción específica.
except, atrapa (cacha) la excepción lanzada por try, contiene las instrucciones que se ejecutan en caso de que la excepción ocurra.
else, este bloque se ejecuta si no ocurre ninguna excepción.
finally, bloque que se ejecuta independientemente de si ocurre o no una excepción.
Ahora si, rescribimos la función factorial con el manejo de excepción de números negativos
def factorial(n):
try:
if n <0:
raise Exception('negative')
except Exception as inst:
print('Error: factorial of {} numbers is not defined!'.format(inst))
else:
if n==0:
return 1
return n*factorial(n-1)
print(factorial(5))
print(factorial(0))
print(factorial(-1))
120
1
Error: factorial of negative numbers is not defined!
None
Manejamos la excepción para enteros negativos. Pero, ¿que pasa con los siguientes casos?
print(factorial(3.1))
print(factorial('hola'))
print(factorial([3,4]))
print(factorial(3+7j))
Se deja el ejercicio de escribir el manejo de la exepción para parámetros no enteros en general.
Ejemplo tomado de la documentación de Python
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print("division by zero!")
else:
print("result is", result)
finally:
print("executing finally clause")
ver el código a continuación
file = 'file.txt'
try:
f = open(file, 'r')
except IOError as error:
print("{} could not be opened: {}".format(file, error.strerror))
else:
A = print('we can read {1} \nwith object-file: {0}'.format(f, file))
finally:
print("Done processing %s" % file)
we can read file.txt
with object-file: <_io.TextIOWrapper name='file.txt' mode='r' encoding='UTF-8'>
Done processing file.txt
Propósito: crear el directorio con el nombre actual mydirectory en el directorio de trabajo.
Se prueba que el directorio con el nombre actual no exista, si existe le agrega al nombre actual la cadena _nx (de ‘next’). La prueba de existencia y extensión de nombre se iteran hasta que el nombre actual del directorio no exista, en ese momento se lanza la excepción. El manejo de la excepción consiste en crear el directorio inexistente con el nombre actual.
import os
newdir = r'./mydirectory'
while True:
try:
os.stat(newdir)
# extending the name to make it unique
newdir= newdir+'_nx'
except:
os.mkdir(newdir)
break
Antes de crear el directorio, debemos revisar que éste no exista. Esto requiere de una implementación del tipo do-while.
Como en Python no existe el estatuto do-while, la estructura presentada puede ser base de su implementación.
while True:
try:
wait_here_until_condition_fails()
except:
once_conditions_fails_do_something_and_exit()
break
Con assert se establecen las condiciones que se debe cumplir un momento determinado, en caso contratio se producirá una excepción AssertionError, que hay que atrapar y procesar adecuadamente
def factorial(n):
try:
assert (int(n)==n & n>=0)
if n==0:
return 1
return n*factorial(n-1)
except AssertionError:
print('Error: factorial is only defined for non-negative integers')
F = factorial(-5)
print(F)
Error: factorial is only defined for non-negative integers
None
La forma corta, y simple, de assert es
assert expression, argument
Usando esta forma, el ejemplo del factorial queda:
def factorial(n):
assert (int(n)==n & n>=0), 'Error: factorial is only defined for non-negative integers'
if n==0:
return 1
return n*factorial(n-1)
Cuando invocamos a la función con un parámetro inválido, se genera una execpción. Si la excepción no es atrapada, tenemos un error de ejecución, como vemos a continuación:
F = factorial(-5)
print(F)
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-29-43eacdb6362f> in <module>()
----> 1 F = factorial(-5)
2 print(F)
<ipython-input-27-38709b56c52e> in factorial(n)
1 def factorial(n):
----> 2 assert (int(n)==n & n>=0), 'Error: factorial is only defined for non-negative integers'
3 if n==0:
4 return 1
5 return n*factorial(n-1)
AssertionError: Error: factorial is only defined for non-negative integers
El mensaje de excepción atrapado en un nivel superior puede servir para depurar nuestro programa mediante mensajes o almacenarlo en un archivo .log; ver el módulo logging.
try:
F = factorial(-5)
print(F)
except AssertionError as error:
print(error)
else:
print('Fin sin excepción')
Error: factorial is only defined for non-negative integers
La ventaja de enviar los mensajes de excepción para que sean procesados a un nivel superior es que, generalmente, el programador de la función a la que es invocada (nivel de librería) desconoce la gravedad del contexto en que ocurre la excepción. Esto es, si el error es catastrófico o no. Por ello no es conveniente dejar a la librería la responsabilidad de terminar la ejecución de un programa que la invoca.
Por ejemplo, imaginemos que estamos en un programa interactivo y deseamos guardar los resultados parciales en archivo temporal, pero resulta que ya existe un archivo previo con el nombre que hemos elegido, por lo que no se pueded crear el nuevo archivo y se genera la excepción de “falla de crear archivo nuevo myfile.dat”. Esto no deberia ser razón para que la librería de manejo de archivos terminará la ejecución del programa, sino para tener la oportunidad de elegir otro nombre. Por supuesto que podemos encontrar un ejemplo en el que la falla de crear un archivo de salida sea catastrófica y debemos detener la ejecución. En cual de los dos casos estemos, dependerá del diseño del programa que usa la librería de manejo de archivos.