W3docs

Python raise y Excepciones Personalizadas

Aprende a usar la sentencia raise de Python, encadenar excepciones con raise...from y crear clases de excepción personalizadas para un manejo de errores claro.

Python te permite hacer más que capturar errores — también puedes señalarlos deliberadamente con la sentencia raise y crear tus propios tipos de excepción para representar problemas específicos del dominio. Este capítulo amplía Python Try...Except y cubre:

  • La sentencia raise — lanzar excepciones integradas
  • Relanzar excepciones dentro de un bloque except
  • Encadenamiento de excepciones con raise ... from
  • Crear clases de excepción personalizadas
  • Construir una jerarquía de excepciones para una aplicación real
  • La sentencia assert y cuándo usarla

La Sentencia raise

La sentencia raise te permite lanzar una excepción en cualquier punto de tu código. La forma más común pasa una instancia de excepción con un mensaje descriptivo:

raise ExceptionType("message")

Usa raise cuando tu código detecte un problema que el llamador debe gestionar. Por ejemplo, una función que acepta una edad debería rechazar valores negativos inmediatamente en lugar de continuar silenciosamente:

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    return age

try:
    set_age(-1)
except ValueError as e:
    print(e)
# Output: Age cannot be negative

Elegir la Excepción Integrada Correcta

Los tipos de excepción integrados de Python tienen un significado concreto. Elegir el correcto hace que tu API sea más fácil de entender y permite a los llamadores manejar distintas categorías de error por separado.

ExcepciónCuándo lanzarla
ValueErrorEl argumento tiene el tipo correcto pero un valor inválido (age = -1)
TypeErrorEl argumento tiene el tipo incorrecto (age = "old")
KeyErrorFalta una clave de diccionario requerida
IndexErrorEl índice de una secuencia está fuera de rango
FileNotFoundErrorUn archivo requerido no existe
PermissionErrorEl proceso carece de los permisos para realizar una operación
RuntimeErrorUn problema general en tiempo de ejecución que no encaja en un tipo más específico
NotImplementedErrorUn método existe en una clase base pero debe ser sobreescrito

Lanzar ValueError por un valor incorrecto es mucho más informativo que lanzar una Exception genérica, porque los llamadores pueden escribir except ValueError para manejar exactamente ese caso.

Relanzar una Excepción

A veces quieres hacer algo con una excepción — registrarla, liberar un recurso — y luego dejar que la misma excepción se propague al llamador sin cambios. Llama a raise sin argumentos dentro de un bloque except para relanzar la excepción actual:

def read_config(path):
    try:
        with open(path) as f:
            return f.read()
    except FileNotFoundError:
        print(f"Warning: config file not found at {path}")
        raise  # re-raise the original FileNotFoundError

try:
    read_config("missing.cfg")
except FileNotFoundError as e:
    print(f"Caught: {e}")
# Output:
# Warning: config file not found at missing.cfg
# Caught: [Errno 2] No such file or directory: 'missing.cfg'

Usar raise sin argumentos preserva el traceback original, lo que facilita mucho la depuración en comparación con capturar y relanzar e como una nueva excepción.

Encadenamiento de Excepciones con raise ... from

Cuando capturas una excepción y lanzas una diferente, Python registra automáticamente la excepción original como el contexto de la nueva. Puedes hacer esta relación explícita — y significativa — usando raise NewException from original:

def load_data(path):
    try:
        with open(path) as f:
            return f.read()
    except OSError as e:
        raise RuntimeError("Failed to load configuration") from e

try:
    load_data("config.json")
except RuntimeError as e:
    print(f"Error: {e}")
    print(f"Caused by: {e.__cause__}")
# Output:
# Error: Failed to load configuration
# Caused by: [Errno 2] No such file or directory: 'config.json'

Cuando Python imprime el traceback, muestra ambas excepciones en orden, dejando claro que el RuntimeError fue una consecuencia directa del OSError. Esto es especialmente útil en código de biblioteca cuando quieres traducir errores de bajo nivel del sistema operativo en errores de dominio de mayor nivel sin ocultar la causa raíz.

Suprimir la Cadena con raise ... from None

En ocasiones, la excepción original es un detalle de implementación que no quieres exponer. Pasa None como causa para ocultarla:

def fetch(url):
    try:
        raise ConnectionError("timeout")
    except ConnectionError:
        raise RuntimeError("Network unavailable") from None

try:
    fetch("http://example.com")
except RuntimeError as e:
    print(f"Error: {e}")
    print(f"Cause hidden: {e.__cause__}")
# Output:
# Error: Network unavailable
# Cause hidden: None

El traceback solo mostrará el RuntimeError. Usa esto con moderación — ocultar la causa raíz dificulta la depuración para los consumidores de la biblioteca.

Crear Clases de Excepción Personalizadas

Las excepciones integradas cubren errores de programación comunes, pero son demasiado genéricas para problemas de dominio. Si tu aplicación de comercio electrónico lanza un ValueError simple cuando falla un pago, los llamadores no pueden distinguirlo de un argumento de función incorrecto. Las clases de excepción personalizadas resuelven esto.

Una excepción personalizada es simplemente una clase que hereda de Exception (o de una de sus subclases):

class InsufficientFundsError(Exception):
    """Raised when a bank account has insufficient funds."""
    def __init__(self, amount, balance):
        self.amount = amount
        self.balance = balance
        super().__init__(
            f"Cannot withdraw {amount}: balance is only {balance}"
        )

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(amount, self.balance)
        self.balance -= amount
        return self.balance

account = BankAccount(100)
try:
    account.withdraw(150)
except InsufficientFundsError as e:
    print(e)
    print(f"You tried to withdraw: {e.amount}")
    print(f"Available balance:     {e.balance}")
# Output:
# Cannot withdraw 150: balance is only 100
# You tried to withdraw: 150
# Available balance:     100

Puntos clave sobre este patrón:

  • super().__init__(message) establece la cadena legible por humanos que devuelve str(e).
  • Los atributos adicionales (self.amount, self.balance) permiten a los llamadores acceder a datos estructurados de la excepción, no solo a una cadena.
  • Un docstring claro documenta cuándo debe lanzarse la excepción.

Construir una Jerarquía de Excepciones

Las aplicaciones reales suelen tener muchos tipos de error relacionados. Agruparlos bajo una clase base compartida permite a los llamadores capturar el error específico o toda la categoría:

class AppError(Exception):
    """Base class for all application errors."""

class ValidationError(AppError):
    """Raised when user input fails validation."""

class DatabaseError(AppError):
    """Raised when a database operation fails."""

def validate_username(name):
    if len(name) < 3:
        raise ValidationError(f"Username '{name}' is too short (min 3 chars)")

try:
    validate_username("ab")
except ValidationError as e:
    print(f"Validation failed: {e}")
except AppError as e:
    print(f"Application error: {e}")
# Output:
# Validation failed: Username 'ab' is too short (min 3 chars)

Un llamador que solo quiera capturar errores de base de datos puede escribir except DatabaseError. Un llamador que quiera capturar cualquier problema de tu biblioteca puede escribir except AppError. Esto refleja el diseño de la propia jerarquía de excepciones de Python, donde OSError agrupa FileNotFoundError, PermissionError y varios más.

Pautas para Excepciones Personalizadas

  • Hereda de Exception, no de BaseException. BaseException es la raíz de la jerarquía de Python e incluye también SystemExit y KeyboardInterrupt, que no deberían capturarse accidentalmente.
  • Termina el nombre de la clase en Error para las excepciones que señalan un problema. Esto sigue la nomenclatura propia de Python (ValueError, TypeError, IOError).
  • Mantén la clase mínima a menos que necesites atributos adicionales. Un cuerpo vacío con un docstring es perfectamente válido.
  • Coloca las excepciones en un módulo dedicado (por ejemplo, exceptions.py) en proyectos grandes para que los llamadores puedan importarlas sin cargar el resto del código.

La Sentencia assert

assert es una forma ligera de expresar invariantes — condiciones que deben ser verdaderas para que tu código sea correcto:

def divide(a, b):
    assert b != 0, "Divisor must not be zero"
    return a / b

try:
    divide(10, 0)
except AssertionError as e:
    print(f"AssertionError: {e}")

print(divide(10, 2))
# Output:
# AssertionError: Divisor must not be zero
# 5.0

assert condition, message lanza AssertionError con el mensaje dado cuando condition es False.

Limitación importante: Python elimina las sentencias assert cuando se ejecuta con el indicador -O (optimize). Esto significa:

  • Usa assert solo para comprobaciones de consistencia interna y ayudas de depuración.
  • Usa raise con una excepción adecuada para la validación de entradas del usuario y las comprobaciones de API pública que siempre deben ejecutarse.

Errores Comunes

Capturar y silenciar excepciones

# Bad — the error disappears
try:
    result = risky_operation()
except Exception:
    pass

# Better — at minimum, log or re-raise
try:
    result = risky_operation()
except Exception as e:
    print(f"Operation failed: {e}")
    raise

Lanzar una cadena en lugar de una excepción

# Wrong — strings are not exceptions
raise "something went wrong"  # TypeError

# Correct
raise ValueError("something went wrong")

Capturar BaseException accidentalmente

# Dangerous — this catches KeyboardInterrupt and SystemExit too
except BaseException:
    ...

# Use Exception instead
except Exception:
    ...

Resumen

TécnicaCuándo usarla
raise ExceptionType("msg")Señalar un problema que el llamador debe gestionar
raise (sin argumentos)Relanzar la excepción actual después de registrarla o limpiar recursos
raise NewError(...) from originalTraducir un error de bajo nivel en uno de mayor nivel, preservando la causa
raise NewError(...) from NoneTraducir un error ocultando la causa interna
Clase de excepción personalizadaDar a los errores específicos del dominio un tipo único y capturable
Jerarquía de excepcionesPermitir a los llamadores capturar categorías de errores amplias o específicas
assertVerificar invariantes internas solo durante el desarrollo

Para ver el panorama completo de cómo capturar y gestionar excepciones, consulta Python Try...Except. Para entender cómo encajan las excepciones personalizadas en el diseño de clases, revisa Python Classes and Objects y Python Inheritance.

Práctica

Práctica
Which statement correctly raises a ValueError with the message 'invalid input'?
Which statement correctly raises a ValueError with the message 'invalid input'?
Práctica
What does bare raise (with no argument) do inside an except block?
What does bare raise (with no argument) do inside an except block?
Práctica
Which base class should a custom exception inherit from?
Which base class should a custom exception inherit from?
Was this page helpful?