EAFP, Easier to Ask Forgiveness than Permission, o por su traducción al español, más vale pedir perdón que pedir permiso, es uno de los principio fundamentales del lenguaje de programación Python, el cual sin duda nos alienta a dejar un poco de lado las validaciones para apoyarnos más de las excepciones, que al final del día, el mantra de Python nos indica que las excepciones no deben pasar desapercibidas (Errors should never pass silently).
Es por ello que en esta ocasión me gustaría que habláramos un poco sobre como Python maneja las excepciones, y por supuesto cómo podremos sacarles el máximo provecho posible, teniendo así un código mucho más pythonico.
Bien, sin más dilación comencemos.
Excepciones en Python
Antes de entrar de lleno con las excepciones en Python, definamos exactamente ¿Qué es una excepción? verás, una excepción, en términos simples, no es más que un error el cual ocurre durante la ejecución de un programa, quizás no se encontró algún archivo, la computadora se quedo sin memoria, o simplemente no fue posible realizar alguna operación, una división entre 0 tal vez. 🙅♀️
Los errores siempre podrán presentarse en nuestros programas, esto es algo cuestionable, lo interesante aquí es ver como cada lenguaje de programación maneja, a su estilo, los errores. Veamos un par de ejemplos.
Uno de los lenguajes que más me gustan, en cuanto al manejo de errores, es sin duda go. Donde las excepciones no se lanzan de forma automática, si no que se espera a que sean comprobadas. Esto va más dirigido al principio: Look Before You Leap, piensa antes de actuar (Yo sé, no es una traducción literal 🤐).
result := superTask()
if err != nil {
fmt.Println("Panicking!")
}
Otro ejemplo pudiese ser JavaScript, donde el lenguaje soporta 6 tipos de excepciones.
var msg = {};
try {
if( my_string.length < 6 ) throw 'SHORT';
if( my_string.length > 10 ) throw 'LONG';
msg.status = 'Pass Validated';
} catch( err ) {
if( err == 'SHORT' ) msg.status = 'Pass is too short';
if( err == 'LONG' ) msg.status = 'Pass is too long';
} finally {
console.log( 'Password evaluated: ' + msg.status );
}
y ahora, ¿Qué pasa con Python? Como mencione anteriormente Python hace hincapié en el uso de excepciones, con lo cual, el código se vuelve mucho más fácil de testear.
Para trabajar excepciones en Python haremos uso de cuatro bloques, de los cuales, es importante mencionar, dos son completamente opcionales.
- try (Obligatorio) Bloque de código el cual se desea ejecutar. Posiblemente lance una excepción.
- except (Obligatorio) Bloque para manejar la excepción.
- else (Opcional) Bloque el cual se ejecutará siempre y cuando no se haya lanzado ninguna excepción.
- finally (Opcional) Bloque que siempre se ejecutará, se haya o no lanzado una excepción.
Veamos un pequeño ejemplo.
try:
dividendo = int(input('Ingresa un número para el dividendo: '))
divisor = int(input('Ingresa un número para el divisor: '))
resultado = dividendo / divisor
except Exception as e:
print('Error - No fue posible completar la operación.')
else:
print("El resultado es", resultado)
finally:
print('Operación finalizada.')
En este caso al colocar except Exception as e: le indicamos a Python que si ocurre algún error de tipo Exception (El error más genérico que puede existir) se imprima en consola 'Error - No fue posible completar la operación.
Esto funciona sin ningún problema. En dado caso el programa no pueda continuar, ya sea por que se ingreso 0 en el divisor o el usuario ingreso, vía teclado, un valor el cual no pueda ser convertido a un entero o por cualquier otro motivo, la excepción es lanzada. Sin embargo, creo que es posible mejorar aún más el código, indicando, muy puntualmente, que hacer con un error en particular.
try:
dividendo = int(input('Ingresa un número para el dividendo: '))
divisor = int(input('Ingresa un número para el divisor: '))
resultado = dividendo / divisor
except ValueError as e:
print('Error - No es posible convertir a un número entero.')
except ZeroDivisionError as e:
print('Error - No posible dividir entre 0.')
else:
print("El resultado es", resultado)
finally:
print('Operación finalizada.')
En este caso podemos observar que ahora trabajamos con dos tipos de error ValueError y ZeroDivisionError, errores que son lanzados cuando no es posible convertir un string a un número entero o cuando se intenta dividir entre 0.
Lo interesante de Python es que es posible lanzar un excepción en cualquier momento, utilizando la palabra reservada raise, seguido de la excepción misma. Veamos un ejemplo, lanzamos una excepción cuando el divisor sea un número negativo.
try:
dividendo = int(input('Ingresa un número para el dividendo: '))
divisor = int(input('Ingresa un número para el divisor: '))
if divisor < 0:
raise Exception
resultado = dividendo / divisor
except Exception as e:
print('Error - No es posible dividir por un número negativo.')
except ValueError as e:
print('Error - No es posible convertir a un número entero.')
except ZeroDivisionError as e:
print('Error - No posible dividir entre 0.')
else:
print("El resultado es", resultado)
finally:
print('Operación finalizada.')
En este caso agregamos una simple condición, si el divisor es menor que 0 lanzamos una excepción de tipo Exception.
Lo interesante de Python es que no estamos limitados únicamente a la excepciones que el lenguaje nos ofrece, no, para nada, de hecho, sí así lo deseamos, podremos crear nuestras propias excepciones, basta con heredar de la clase Exception.
class NegativeNumberError(Exception):
pass
try:
dividendo = int(input('Ingresa un número para el dividendo: '))
divisor = int(input('Ingresa un número para el divisor: '))
if divisor < 0:
raise NegativeNumberError
resultado = dividendo / divisor
except Exception as e:
print('Error - No es posible dividir por un número negativo.')
except ValueError as e:
print('Error - No es posible convertir a un número entero.')
except ZeroDivisionError as e:
print('Error - No posible dividir entre 0.')
else:
print("El resultado es", resultado)
finally:
print('Operación finalizada.')
Como podemos observar en este tercer ejemplo, la estructura para el manejo de excepciones, en Python, no esta limitada únicamente y exclusivamente al manejo de un solo error, para nada, podemos manejar la n cantidad de error que deseemos, los nuestros incluidos.
Ahora, quizás te estés preguntando, ¿por qué si la clase tiene por nombre NegativeNumberError la excepción se maneja para un tipo Exception? La respuesta es muy sencilla, por que la clase hereda de Exception. Si queremos ser muy puntuales basta con modificar ese bloque de código.
except NegativeNumberError as e:
print('Error - No es posible dividir por un número negativo.')
Ventajas y desventajas
Listo, ya sabemos manejar excepciones, pero, ¿Cuáles son sus ventajas y desventajas? primero que nada hay que dejar en claro que el manejo de excepciones en Python es muy bueno, sin embargo no se debe abusar de ello. No en todos los casos aplicaremos excepciones, por ejemplo:
En Django existen los métodos get y filter los cuales, respectivamente, nos permiten obtener, ya un elemento en concreto o un listado de. Aunque ambos métodos realizan consultas a la base de datos su funcionamiento es completamente diferente, por su parte el método get retorna una excepción en caso no existe registro alguno que cumpla con la condición, caso contrario que con el método filter, el cual solo retorna una lista vacía.
A partir de esto podremos tomar una decisión sobre qué método utilizar para realizar una consulta.
from users.models import User
try:
user = User.objects.get(pk=9999)
except User.DoesNotExist as ex:
print('No fue posible encontrar un usuario')
from users.models import User
user = User.objects.filter(pk=9999).first()
if user is None:
print('No fue posible encontrar un usuario')
En ambos casos obtendremos el mismo resultado, ya sea validando o simplemente apoyándonos de un try y un except.
A lo que quiero llegar con todo esto, es que utilizar excepciones en todas parte hará que nuestro programa sea difícil de leer e inclusive pueda llegar a tener problemas de performance, por que sí, utilizar excepciones es algo lento. Sin embargo utilizar las excepciones de manera correcta no solo hará que nuestro código sea más pythonico, si no que será fácil de testear, y dependiendo los casos, fácil de leer,
El problema aquí es indicar donde puntualmente debemos utilizar, o no, excepciones. Es algo que con la práctica sin duda iremos mejorando, pero recuerda, en Python es mejor pedir perdón que pedir permiso. 🐍