Excepciones
¿Qué es una excepción en Python?
Una excepción es un error en tiempo de ejecución. A pesar que el programa no tenga errores de sintaxis y la lógica de programación esté perfectamente implementada, es posible que surjan situaciones no previstas por el programador que generen un error haciendo que el programa se detenga y no siga su normal funcionamiento, a estos errores en tiempo de ejecución se les conocen como excepciones.
Consideremos el siguiente programa que realiza las 4 operaciones aritméticas básicas:
def suma(a,b):
return a+b
def resta(a,b):
return a-b
def multiplicacion(a,b):
return a*b
def division(a,b):
return a/b
print('* * * Programa de operaciones básicas * * *')
iValorA = int(input('Introduzca el primer operando: '))
iValorB = int(input('Introduzca el segundo operando: '))
op = input('Escriba la operación que desea realizar: (suma, resta, multiplicacion o division)')
if op == 'suma':
print(f'La suma da {suma(iValorA,iValorB)} como resultado')
elif op == 'resta':
print(f'La resta da {resta(iValorA,iValorB)} como resultado')
elif op == 'multiplicacion':
print(f'La multiplicación da {multiplicacion(iValorA,iValorB)} como resultado')
elif op == 'division':
print(f' La división da {division(iValorA,iValorB)} como resultado')
else:
print(f'Error: Operación {op} no contemplada')
print('* * * Operación terminada * * *')
Si ingresamos como segundo valor 0 y escribimos en operación a realizar division, se va a producir un error en tiempo de ejecución, ya que matemáticamente no se puede dividir entre 0 ya que el resultado sería infinito y el programa se detendría no mostrando la última línea de código. Provoquemos a excepción para comprobar o dicho anteriormente:
Resultado ingresando 4 y 0 para realizar la operación de división:
* * * Programa de operaciones básicas * * *
Introduzca el primer operando: 4
Introduzca el segundo operando: 0
Escriba la operación que desea realizar: (suma, resta, multiplicacion o division)division
Traceback (most recent call last):
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 29, in <module>
print(f' La división da {division(iValorA,iValorB)} como resultado')
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 14, in division
return a/b
ZeroDivisionError: division by zero
Como podemos comprobar, el intérprete de Python ejecutó correctamente el programa hasta que se le pidió que realizara una división entre 0, lo cual es un error matemático, motivo por el cual se muestra el error por pantalla (ZeroDivisionError: division by zero) deteniéndose por completo la ejecución del programa en ese punto y sin ejecutar la última línea. Esto es lo que se conoce como excepción y ocurre a pesar que el programa había funcionado anteriormente y aparentemente su lógica era correcta, solo que esta vez ocurrió una situación excepcional que produjo un error inesperado.
CAPTURA O CONTROL DE EXCEPCIONES
En los casos que se presente una excepción en Python, es posible hacer una captura de excepciones para solventar la situación y evitar que el programa entero falle, este control consiste en que la línea en la que se produjo el error probablemente no se ejecute pero el programa no se detenga y siga adelante.
Para implementar un control de excepciones en Python basta con incluir la línea de código que potencialmente puede generar el error dentro de un bloque TRY. La sintaxis del bloque TRY para capturar excepciones es como sigue:
TRY:
<código potencialmente generador de errores en tiempo de ejecución>
EXCEPT <tipo de error>:
<código que se ejecutará si se a producido una excepción>
Veamos como se implementaría una captura de excepciones en el programa de operaciones aritméticas básicas:
def suma(a,b):
return a+b
def resta(a,b):
return a-b
def multiplicacion(a,b):
return a*b
def division(a,b):
try:
return a/b
except ZeroDivisionError:
print('Error: No se puede dividir ningún número entre 0')
print('* * * Programa de operaciones básicas * * *')
iValorA = int(input('Introduzca el primer operando: '))
iValorB = int(input('Introduzca el segundo operando: '))
op = input('Escriba la operación que desea realizar: (suma, resta, multiplicacion o division)')
if op == 'suma':
print(f'La suma da {suma(iValorA,iValorB)} como resultado')
elif op == 'resta':
print(f'La resta da {resta(iValorA,iValorB)} como resultado')
elif op == 'multiplicacion':
print(f'La multiplicación da {multiplicacion(iValorA,iValorB)} como resultado')
elif op == 'division':
print(f' La división da {division(iValorA,iValorB)} como resultado')
else:
print(f'Error: Operación {op} no contemplada')
print('* * * Operación terminada * * *')
Resultado ingresando 4 y 0 para realizar la operación de división:
* * * Programa de operaciones básicas * * *
Introduzca el primer operando: 4
Introduzca el segundo operando: 0
Escriba la operación que desea realizar: (suma, resta, multiplicacion o division)division
Error: No se puede dividir ningún número entre 0
La división da None como resultado
* * * Operación terminada * * *
Como podemos comprobar ahora el programa captura la excepción, muestra un mensaje al usuario indicando que no puede realizar la operación solicitada pero no se detiene y sigue su ejecución normalmente hasta llegar al final del programa. Hay que tomar en cuenta que si el error que se genera es diferente al previsto el programa fallará igualmente, con lo que habría que preveer otras posibles excepciones o incluirlas en el código cuando lleguen a producirse.
Veamos otro ejemplo de excepciones que pueden aparecer en el código. Si ingresamos un caracter alfabético en lugar de un valor numérico en los operandos se generaría otro error:
Resultado ingresando valor 'a' como primer operando:
* * * Programa de operaciones básicas * * *
Introduzca el primer operando: a
Traceback (most recent call last):
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 21, in <module>
iValorA = int(input('Introduzca el primer operando: '))
ValueError: invalid literal for int() with base 10: 'a'
Como era de esperarse, introducir un caracter alfabético como operando en el programa genera un error, en este caso Python advierte que se produjo una excepción llamada ValueError. Para solucionarlo basta con repetir el procedimiento aplicado en el caso anterior usando un ciclo TRY EXCEPT en la línea que potencialmente genera esa excepción, veamos como quedaría:
def suma(a,b):
return a+b
def resta(a,b):
return a-b
def multiplicacion(a,b):
return a*b
def division(a,b):
try:
return a/b
except ZeroDivisionError:
print('Error: No se puede dividir ningún número entre 0.')
print('* * * Programa de operaciones básicas * * *')
try:
iValorA = int(input('Introduzca el primer operando: '))
iValorB = int(input('Introduzca el segundo operando: '))
except ValueError:
print('Error: El dato introducido no es un valor numérico.')
op = input('Escriba la operación que desea realizar: (suma, resta, multiplicacion o division)')
if op == 'suma':
print(f'La suma da {suma(iValorA,iValorB)} como resultado')
elif op == 'resta':
print(f'La resta da {resta(iValorA,iValorB)} como resultado')
elif op == 'multiplicacion':
print(f'La multiplicación da {multiplicacion(iValorA,iValorB)} como resultado')
elif op == 'division':
print(f' La división da {division(iValorA,iValorB)} como resultado')
else:
print(f'Error: Operación {op} no contemplada')
print('* * * Operación terminada * * *')
Resultado ingresando 'a' como primer operando:
* * * Programa de operaciones básicas * * *
Introduzca el primer operando: a
Error: El dato introducido no es un valor numérico.
Escriba la operación que desea realizar: (suma, resta, multiplicacion o division)
Como podemos ver ahora el programa no se cae pero luego de la captura de la excepción y el mensaje de error, sigue su curso pidiendo que operación realizar, cosa que no tiene sentido y que seguramente conllevará a otras excepciones. Con lo cual una posible solución sería la creación de un bucle infinito hasta que no se genere dicha excepción y el programa así se asegure que va a pasar a la siguiente fase con datos válidos. Veamos como quedaría:
def suma(a,b):
return a+b
def resta(a,b):
return a-b
def multiplicacion(a,b):
return a*b
def division(a,b):
try:
return a/b
except ZeroDivisionError:
print('Error: No se puede dividir ningún número entre 0.')
print('* * * Programa de operaciones básicas * * *')
while True:
try:
iValorA = int(input('Introduzca el primer operando: '))
iValorB = int(input('Introduzca el segundo operando: '))
break
except ValueError:
print('Error: El dato introducido no es un valor numérico.')
op = input('Escriba la operación que desea realizar: (suma, resta, multiplicacion o division)')
if op == 'suma':
print(f'La suma da {suma(iValorA,iValorB)} como resultado')
elif op == 'resta':
print(f'La resta da {resta(iValorA,iValorB)} como resultado')
elif op == 'multiplicacion':
print(f'La multiplicación da {multiplicacion(iValorA,iValorB)} como resultado')
elif op == 'division':
print(f' La división da {division(iValorA,iValorB)} como resultado')
else:
print(f'Error: Operación {op} no contemplada')
print('* * * Operación terminada * * *')
Resultados ingresando caracteres alfabéticos aleatorios:
* * * Programa de operaciones básicas * * *
Introduzca el primer operando: a
Error: El dato introducido no es un valor numérico.
Introduzca el primer operando: c
Error: El dato introducido no es un valor numérico.
Introduzca el primer operando: qwe
Error: El dato introducido no es un valor numérico.
Introduzca el primer operando: 2
Introduzca el segundo operando: ff
Error: El dato introducido no es un valor numérico.
Introduzca el primer operando: g
Error: El dato introducido no es un valor numérico.
Introduzca el primer operando: 2
Introduzca el segundo operando: t
Error: El dato introducido no es un valor numérico.
Introduzca el primer operando: 2
Introduzca el segundo operando: 3
Escriba la operación que desea realizar: (suma, resta, multiplicacion o division)
Como podemos comprobar, solamente al ingresar valores válidos en ambos operandos es que el programa pasa a la siguiente fase gracias al bucle infinito WHILE que encuentra su salida en la sentencia BREAK luego de asignar correctamente valores enteros a las variables iValorA e iValorB. Nótese que el bloque TRY EXCEPT abarca el ingreso de ambos operadores, con lo cual si se ingresa solamente un valor válido igualmente se genera la excepción y se repite el bucle hasta que ambos valores sean válidos.
Captura de múltiples excepciones
Es posible definir varias condiciones para detectar múltiples excepciones en un mismo código. Veamos el siguiente ejemplo:
def division():
fValorA = float(input('Ingrese el primer operando: '))
fValorB = float(input('Ingrese el segundo operando: '))
print('El resultado de la división es: ' + str(fValorA/fValorB))
division()
print('* * * Fin de programa * * *')
Como es de esperarse la función division() potencialmente puede generar excepciones de los dos tipos vistos anteriormente, division entre 0 y operador no válido si no es numérico. Para capturar ambas excepciones en un mismo bloque TRY basta con especificar las condiciones una tras otra dentro del bloque. Veamos como quedaría:
def division():
try:
fValorA = float(input('Ingrese el primer operando: '))
fValorB = float(input('Ingrese el segundo operando: '))
print('El resultado de la división es: ' + str(fValorA/fValorB))
except ZeroDivisionError:
print('Error: división entre 0 no es posible')
except ValueError:
print('Error: Valores introducios no válidos')
division()
print('* * * Fin de programa * * *')
Resultado ingresando operandos 2 y 3:
Ingrese el primer operando: 2
Ingrese el segundo operando: 3
El resultado de la división es: 0.6666666666666666
* * * Fin de programa * * *
Resultado ingresando operandos 5 y 0:
Ingrese el primer operando: 5
Ingrese el segundo operando: 0
Error: división entre 0 no es posible
* * * Fin de programa * * *
Resultado ingresando 'a' como primer operando:
Ingrese el primer operando: a
Error: Valores introducios no válidos
* * * Fin de programa * * *
Resultado ingresando 3 como primer operando y 'a' como segundo operando:
Ingrese el primer operando: 3
Ingrese el segundo operando: a
Error: Valores introducios no válidos
* * * Fin de programa * * *
Como podemos comprobar se ha previsto 2 posibles excepciones en el ingreso de dos operandos para hacer una división con sus posibles combinaciones de excepciones.
Existe una manera de captura cualquier error que se genere, sin embargo no es muy recomendable ya que no hay manera de darle información precisa al usuario. La manera sería englobar las líneas potencialmente generadoras de excepciones con la instrucción TRY EXCEPT pero sin especificar la descripción del error. Veamos como quedaría con el ejemplo anterior:
def division():
try:
fValorA = float(input('Ingrese el primer operando: '))
fValorB = float(input('Ingrese el segundo operando: '))
print('El resultado de la división es: ' + str(fValorA/fValorB))
except:
print('* * * Se ha producido un error * * *')
division()
print('* * * Fin de programa * * *')
Resultado ingresando un caracter alfabético como operando:
Ingrese el primer operando: a
* * * Se ha producido un error * * *
* * * Fin de programa * * *
Resultado ingresando 0 como segundo operando:
Ingrese el primer operando: 3
Ingrese el segundo operando: 0
* * * Se ha producido un error * * *
* * * Fin de programa * * *
Como podemos ver el mensaje es siempre el mismo y no especifica que tipo de error se ha cometido, sin embargo abarca todas las posibilidades de excepciones que puedan darse y evita que e programa se caiga.
La clausula FINALLY
El bloque TRY EXCEPT admite una clausula adicional la cual hace que el código que se encuentra dentro de ella se ejecute necesariamente sin importar si se ha capturado una excepción o no. Veamos un ejemplo del uso de esta clausula en el ejemplo anterior:
def division():
try:
fValorA = float(input('Ingrese el primer operando: '))
fValorB = float(input('Ingrese el segundo operando: '))
print('El resultado de la división es: ' + str(fValorA/fValorB))
except ZeroDivisionError:
print('Error: división entre 0 no es posible')
except ValueError:
print('Error: Valores introducios no válidos')
finally:
print('Se ha completado la función división . . .')
division()
print('* * * Fin de programa * * *')
Resultado provocando una excepción:
Ingrese el primer operando: 2
Ingrese el segundo operando: 0
Error: división entre 0 no es posible
Se ha completado la función división . . .
* * * Fin de programa * * *
Resultado sin excepciones:
Ingrese el primer operando: 3
Ingrese el segundo operando: 2
El resultado de la división es: 1.5
Se ha completado la función división . . .
* * * Fin de programa * * *
Como podemos comprobar la línea perteneciente a la cláusula FINALLY se ejecuta en ambos casos. También es posible el uso de la clausula FINALLY omitiendo las clausulas EXCEPT, en cuyo caso en caso de ocurrir una excepción se ejecutaría el contenido del bloque FINALLY y luego rl programa se detendría mostrando la excepción. Veamos el ejemplo en la práctica:
def division():
try:
fValorA = float(input('Ingrese el primer operando: '))
fValorB = float(input('Ingrese el segundo operando: '))
print('El resultado de la división es: ' + str(fValorA/fValorB))
finally:
print('Se ha completado la función división . . .')
division()
print('* * * Fin de programa * * *')
Resultado provocando una excepción:
Ingrese el primer operando: a
Se ha completado la función división . . .
Traceback (most recent call last):
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 58,
in <module>
division()
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 53,
in division
fValorA = float(input('Ingrese el primer operando: '))
ValueError: could not convert string to float: 'a'
Como podemos comprobar la excepción ha detenido la ejecución del programa pero antes ha ejecutado la línea "print('Se ha completado la función división . . .')", contenida en el bloque FINALLY. Es importante destacar que Python admite el uso del bloque TRY en combinación con la clausula EXCEPT o FINALY, o con ambas a la vez pero nunca TRY sola ya que se el intérprete lo toma como un error de sintaxis.
Lanzamiento de excepciones
Es posible que el programador sea el que de manera intencionada genere una excepción, ya no que se genere dentro del programa y sea Python que lo genere sino que directamente el programador la provoque. Consideremos un programa que le pregunta la edad al usuario y en base a ese número emite una opinión, siendo una edad menor a cero un error y luego considerando los rangos de 0 a 20 como muy joven, de 21 a 40 joven, de 41 a 65 maduro y menor que 100 como de la tercera edad. El programa quedaría de la siguiente forma:
def evalua_edad(n):
if n<0:
print('Error: no puedes tener una edad negativa')
elif n<20:
print('Eres muy joven')
elif n<40:
print('Eres joven')
elif n<65:
print('Eres maduro')
elif n<100:
print('Eres de la tercera edad')
evalua_edad(int(input('Introduce tu edad: ')))
Resultado ingresando -2:
Introduce tu edad: -2
Error: no puedes tener una edad negativa
Resultado ingresando 8:
Introduce tu edad: 8
Eres muy joven
Resultado ingresando 20:
Introduce tu edad: 20
Eres joven
Resultado ingresando 33:
Introduce tu edad: 33
Eres joven
Resultado ingresando 60:
Introduce tu edad: 60
Eres maduro
Resultado ingresando 99:
Introduce tu edad: 99
Eres de la tercera edad
Como podemos ver el programa funciona correctamente. Sin embargo es posible crear una excepción para cuando ocurra edad menor que cero en lugar de mostrar un mensaje en le condicional. Para elo nos valdremos de la palabra clave RAISE la cual genera una excepción en tiempo de ejecución y cuya sintaxis es como sigue:
RAISE <tipo de error predefinido en Python> ("<Mensaje personalizado de error>")
Veamos como funciona en la práctica:
def evalua_edad(n):
if n<0:
raise TypeError('No puedes tener una edad negativa')
elif n<20:
print('Eres muy joven')
elif n<40:
print('Eres joven')
elif n<65:
print('Eres maduro')
elif n<100:
print('Eres de la tercera edad')
evalua_edad(int(input('Introduce tu edad: ')))
Resultado ingresando -1:
Introduce tu edad: -1
Traceback (most recent call last):
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 86,
in <module>
evalua_edad(int(input('Introduce tu edad: ')))
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 77,
in evalua_edad
raise TypeError('No puedes tener una edad negativa')
TypeError: No puedes tener una edad negativa
Como podemos comprobar se ha generado un error del tipo TypeError con el mensaje personalizado "No puedes tener una edad negativa". Sin embargo el programa se ha detenido ya que no se ha capturado este error con un bloque TRY EXCEPT. Hay que tomar en cuenta que en este caso se ha usado el error perteneciente a la clase TypeError de la biblioteca estándard de Python, sin embargo es posible definir nuestras propias clases de errores, sin embargo a efectos prácticos de momento se pueden usar cualquiera de las clases de errores definidas en la biblioteca de Python, veamos el ejemplo con el objeto ZeroDivisionError:
def evalua_edad(n):
if n<0:
raise ZeroDivisionError('No puedes tener una edad negativa')
elif n<20:
print('Eres muy joven')
elif n<40:
print('Eres joven')
elif n<65:
print('Eres maduro')
elif n<100:
print('Eres de la tercera edad')
evalua_edad(int(input('Introduce tu edad: ')))
Resultado ingresando -1:
Introduce tu edad: -1
Traceback (most recent call last):
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 86,
in <module>
evalua_edad(int(input('Introduce tu edad: ')))
File "e:\Documentos\Desarrollos\Python\Curso-Python\16 Excepciones.py", line 77,
in evalua_edad
raise ZeroDivisionError('No puedes tener una edad negativa')
ZeroDivisionError: No puedes tener una edad negativa
Como podemos comprobar el programa genera el error al igual que en el caso anterior pero esta vez del tipo ZeroDivisionError. Lo ideal sería definir nuestra propia biblioteca de errores para que la información mostrada al usuario sea mucho más precisa. Sin embargo es posible personalizar un poco los tipos deerrores de la biblioteca estándard de Python, veamos e siguiente ejemplo:
def evalua_edad(n):
if n<0:
raise ZeroDivisionError('No puedes tener una edad negativa')
elif n<20:
print('Eres muy joven')
elif n<40:
print('Eres joven')
elif n<65:
print('Eres maduro')
elif n<100:
print('Eres de la tercera edad')
try:
evalua_edad(int(input('Introduce tu edad: ')))
except ZeroDivisionError as EdadMenorCero:
print(EdadMenorCero)
Resultado ingresando -2:
Introduce tu edad: -2
No puedes tener una edad negativa
Como podemos ver se le puede dar un alias al error predeterminado de Python usando la clausula AS en la captura de la excepción y luego invocar el mensaje personalizado a través de la función PRINT().
Como nota final de este apartado es necesario destacar a importancia del manejo de errores en programación. Aunque un programa este altamente depurado y libre de errores de programación (cosa que nunca se sabe por mas experimentado que sea el programador) o libre de errores de lógica, siempre pueden darse circunstancias externas al programa que pueden producir errores, como caídas de conexión a internet, archivos que ya no están disponibles, bases de datos que se cierran inesperadamente, etc. Siempre es conveniente preveer estos posibles errores y crear un subsistema de manejo de excepciones donde se le informe al usuario en todo momento con la mayor precisión posible y muy importante sin que se detenga la ejecución del programa sin al menos la posibilidad de avisar al usuario o permitirle hacer alguna acción importante antes de salir del programa.
Comentarios
Publicar un comentario