Excepciones propias en Python: jerarquías de errores con clases personalizadas

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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP