Evite trampas comunes: manejo adecuado de excepciones en Python

Veo a mucha gente manejando excepciones de forma incorrecta. Quizás esto te aplique a ti también. ¿Te suena la siguiente situación?
Estás escribiendo código, pero sabes que la librería que usas podría lanzar una excepción. No recuerdas exactamente cuál. En este punto, es tentador usar bloques catch-all y seguir con lo divertido.
La peor forma de hacerlo
Lo peor que puedes hacer es crear un bloque try-except que capture cualquier cosa. Por catch-all, me refiero a algo como:
try:
...
except:
pass
Los bloques catch-all como estos son malos porque:
- No tienes idea de qué otras excepciones podrían lanzarse (más sobre esto luego).
- Ocultas la excepción usando silenciosamente
passen lugar de registrar el error.
Además, un except vacío capturará todo, incluyendo KeyboardInterrupt (Ctrl+C), SystemExit e incluso NameError ¡Esto significa que el siguiente código no puede detenerse limpiamente:
from time import sleep
while True:
try:
print("Intenta pararme")
sleep(1)
except:
print("No... me... detengas... ahora!")
Siéntete libre de probarlo. Necesitas cerrar la ventana de terminal o matar el proceso de Python para detener este programa.
Una forma algo mejor de capturar todas las excepciones
En cambio, al usar except Exception, aunque sigue siendo una forma rápida y sucia de capturar demasiadas excepciones, al menos podrás detener el proceso en ejecución correctamente:
from time import sleep
while True:
try:
print("Intenta pararme")
sleep(1)
except Exception:
print("Vale, ¡pararé!")
Al capturar Exception no capturarás SystemExit, KeyboardInterrupt y otras excepciones similares. ¿Por qué?
Todas las excepciones heredan de una clase llamada BaseException. Según la documentación oficial: «En una sentencia try con una cláusula except que menciona una clase particular, esa cláusula también maneja cualquier clase de excepción derivada de esa clase». Un except vacío es equivalente a except BaseException, por eso captura todas las excepciones posibles.
En contraste, la clase Exception se define como: «Todas las excepciones integradas, no de salida del sistema, derivan de esta clase. Todas las excepciones definidas por el usuario también deberían derivar de esta clase».
Incluso empeora
En el siguiente ejemplo, usamos la librería os para obtener el directorio de trabajo actual. Sin embargo, mis dedos gorditos cometieron un error tipográfico:
import os
try:
working_dir = os.getcdw()
print(working_dir)
except:
print('error')
Como os.getcdw no es una función en el módulo os, se lanza un NameError. En lugar de fallar, la cláusula except captura el error, imprime «error» y el programa continúa a pesar de nuestro flagrante error tipográfico. ¡Desafortunadamente, ¡tampoco se soluciona capturando Exception!
Aparentemente, nuestro pequeño truco del paso uno no es una solución para todos nuestros problemas. Entonces, ¿qué deberíamos hacer?
Captura lo que puedes manejar
Una frase que se oye a menudo sobre excepciones es: captura lo que puedes manejar. Muchos desarrolladores se sienten tentados a lidiar directamente con excepciones, mientras que a menudo es mejor dejar que la excepción se propague a una parte de tu programa que realmente pueda manejarla.
Por ejemplo, considera la parte de un editor de texto que abre y carga archivos, llamémosla la clase OpenFile. Si el usuario solicita abrir un archivo que no existe, puedes manejar ese error directamente o dejarlo propagar.
En este caso, es mejor propagar la excepción al llamador, porque OpenFile no sabe qué tan grave es esta excepción para el llamador. El llamador puede manejar la situación de múltiples formas:
- Podría crear un nuevo archivo con ese nombre y continuar.
- Quizás el llamador necesita que el archivo exista, en cuyo caso puede mostrar un diálogo de error informando al usuario que el archivo no existe.
De cualquier forma, no corresponde a la clase OpenFile decidir qué hacer en caso de FileNotFoundError.
¿Significa que una excepción siempre debe propagarse? No. Una posible excepción que puede manejarse en la clase FileOpen es TimeoutError. Podrías querer reintentar unas cuantas veces, por ejemplo, sin molestar al llamador con el error. Esta es una excepción que OpenFile puede manejar, así que está bien capturarla y reintentar.
Conclusión
Bajo ninguna circunstancia debes capturar más excepciones de las que puedes manejar. Los bloques except genéricos son receta para bugs y código impredecible. En otras palabras: captura lo que puedas manejar.
Si escribes tu código con el mantra «captura lo que puedes manejar» en mente, escribir bloques catch-all rompe todas las reglas. Por favor, deja de hacerlo. Como ejercicio, podrías revisar parte de tu código existente y ver si puede mejorarse con este nuevo conocimiento.
python
- Domine la capacitación en CMMS: Involucre equipos y aumente la retención
- ¿Contratando? Domine la Oleada de Candidatos en el Mercado Post-Pandemia
- Dominando operadores en Python: Guía completa
- Dominando Errores y Excepciones Integradas en Python
- Dominando Excepciones Personalizadas en Python: Guía Práctica
- Obtener la Fecha y Hora Actual en Python: Guía Práctica
- Función print() en Python: Guía Práctica con Ejemplos
- Diccionarios en Python: Añadir, Actualizar y Gestionar Pares Clave/Valor
- Print en Python sin salto de línea: dominando el parámetro end y otras técnicas
- Calculando Promedios en Python: Guía Práctica con Múltiples Métodos
- Dominando el manejo multi‑excepción en Java