Las excepciones de Python no son solo las que vienen con el lenguaje. Crear tus propias excepciones permite comunicar errores específicos del dominio de tu aplicación, añadir información de contexto y construir jerarquías que facilitan el manejo granular en el código cliente.
Excepción básica heredando de Exception
class ErrorValidacion(Exception):
"""Error lanzado cuando los datos de entrada no son válidos."""
pass
def validar_edad(edad):
if not isinstance(edad, int):
raise ErrorValidacion(f"La edad debe ser un entero, recibido: {type(edad).__name__}")
if edad < 0 or edad > 150:
raise ErrorValidacion(f"Edad fuera de rango: {edad}")
return edad
try:
validar_edad(-5)
except ErrorValidacion as e:
print(f"Validación fallida: {e}")
# Validación fallida: Edad fuera de rango: -5
Añadir atributos al __init__
Las excepciones pueden tener atributos propios que el código cliente puede inspeccionar:
class ErrorCampo(Exception):
def __init__(self, campo, valor, mensaje):
self.campo = campo
self.valor = valor
self.mensaje = mensaje
super().__init__(f"Campo '{campo}' inválido: {mensaje} (recibido: {valor!r})")
class ErrorFormulario(Exception):
def __init__(self, errores):
self.errores = errores # lista de ErrorCampo
super().__init__(f"{len(errores)} error(es) de validación")
def validar_formulario(datos):
errores = []
if not datos.get("nombre"):
errores.append(ErrorCampo("nombre", datos.get("nombre"), "no puede estar vacío"))
if not isinstance(datos.get("edad"), int):
errores.append(ErrorCampo("edad", datos.get("edad"), "debe ser entero"))
if errores:
raise ErrorFormulario(errores)
try:
validar_formulario({"nombre": "", "edad": "veinte"})
except ErrorFormulario as e:
for err in e.errores:
print(f" {err.campo}: {err.mensaje}")
Jerarquía de errores
Una jerarquía permite capturar a distintos niveles de granularidad:
class ErrorApp(Exception):
"""Clase base de todos los errores de la aplicación."""
class ErrorBaseDatos(ErrorApp):
"""Errores relacionados con la base de datos."""
class ErrorConexionBD(ErrorBaseDatos):
def __init__(self, host, puerto, causa=None):
self.host = host
self.puerto = puerto
super().__init__(f"No se pudo conectar a {host}:{puerto}")
if causa:
self.__cause__ = causa
class ErrorConsultaBD(ErrorBaseDatos):
def __init__(self, sql, causa=None):
self.sql = sql
super().__init__(f"Error en consulta: {sql[:50]}...")
# Captura granular
try:
raise ErrorConexionBD("db.prod", 5432)
except ErrorConexionBD as e:
print(f"Problema de conexión a {e.host}:{e.puerto}")
except ErrorBaseDatos:
print("Otro error de BD")
except ErrorApp:
print("Error general de la aplicación")
El patrón raise ... from para encadenar
import json
class ErrorConfiguracion(Exception):
pass
def cargar_config(ruta):
try:
with open(ruta) as f:
return json.load(f)
except FileNotFoundError as e:
raise ErrorConfiguracion(f"No se encontró la config: {ruta}") from e
except json.JSONDecodeError as e:
raise ErrorConfiguracion(f"Config malformada en {ruta}") from e
try:
config = cargar_config("settings.json")
except ErrorConfiguracion as e:
print(f"Error: {e}")
print(f"Causa original: {e.__cause__}")
Crea una clase base de error para tu librería o aplicación y construye una jerarquía desde ahí. Añade atributos en __init__ para toda la información que el código cliente pueda necesitar inspeccionar, y llama siempre a super().__init__(mensaje) para que el mensaje aparezca en el traceback.
