raise и собственные исключения

Ключевое слово raise в Python и создание собственных классов исключений: когда и зачем генерировать ошибки вручную.

raise позволяет намеренно вызвать исключение. Это нужно для валидации данных, сигнализации об ошибочных состояниях и создания понятных API.

Базовое использование raise

def set_age(age):
    if age < 0 or age > 150:
        raise ValueError(f"Недопустимый возраст: {age}")
    return age

print(set_age(25))

try:
    print(set_age(-5))
except ValueError as e:
    print(f"Ошибка: {e}")

Вывод:

25
Ошибка: Недопустимый возраст: -5

Повторное выбрасывание исключения

Иногда нужно обработать исключение частично (например, залогировать) и пробросить его дальше. Голый raise без аргументов повторно бросает текущее исключение:

def load_data(path):
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError as e:
        print(f"[LOG] Файл не найден: {path}")
        raise    # пробрасываем исключение выше

try:
    load_data("missing.txt")
except FileNotFoundError:
    print("Обработано на верхнем уровне")

Вывод:

[LOG] Файл не найден: missing.txt
Обработано на верхнем уровне

Собственные классы исключений

Создание своего класса исключения — просто наследование от Exception:

class InsufficientFundsError(Exception):
    """Ошибка: недостаточно средств на счёте."""
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(
            f"Нельзя снять {amount} руб. — на счёте только {balance} руб."
        )

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

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

account = BankAccount(100)

try:
    account.withdraw(150)
except InsufficientFundsError as e:
    print(e)
    print(f"Баланс: {e.balance}, запрошено: {e.amount}")

Вывод:

Нельзя снять 150 руб. — на счёте только 100 руб.
Баланс: 100, запрошено: 150

Собственное исключение несёт дополнительные данные (баланс, запрошенная сумма), по которым вызывающий код может принять решение. Это главное преимущество кастомных классов перед голым ValueError.

Иерархия собственных исключений

class AppError(Exception):
    """Базовый класс для ошибок приложения."""

class ValidationError(AppError):
    """Ошибка валидации данных."""

class DatabaseError(AppError):
    """Ошибка работы с базой данных."""

def process(data):
    if not data:
        raise ValidationError("Данные не могут быть пустыми")

try:
    process(None)
except AppError as e:
    # поймает ValidationError, DatabaseError и любой AppError
    print(f"Ошибка приложения: {e}")

Вывод:

Ошибка приложения: Данные не могут быть пустыми

Коротко

  • raise ТипОшибки("сообщение") намеренно генерирует исключение.
  • Голый raise внутри except пробрасывает текущее исключение выше.
  • Собственное исключение — класс, наследующий от Exception; может хранить дополнительные данные.
  • Строите иерархию: базовый класс → конкретные ошибки. Это позволяет ловить группу через родителя.
Проверьте себя
1. Что делает голый raise без аргументов внутри блока except?
AСоздаёт новое пустое исключение
BПовторно бросает текущее перехваченное исключение
CИгнорирует исключение
DВызывает SystemExit
2. От какого класса наследуют при создании собственного исключения?
Aobject
BError
CException
DBaseException
3. Зачем создавать собственные классы исключений вместо использования ValueError?
AТолько для красивых имён
BЧтобы ускорить программу
CЧтобы хранить дополнительные данные и позволить точечный перехват
DСобственные исключения не рекомендуется создавать
Поддержать проект