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

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

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

Создание базового класса исключения

Для создания своего исключения достаточно унаследоваться от Exception (или от другого встроенного исключения):

class AppError(Exception):
    """Базовое исключение приложения."""
    pass

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

class NotFoundError(AppError):
    """Запрошенный ресурс не найден."""
    pass

pass в теле — это всё, что нужно для простого исключения. Наследование от AppError позволит перехватывать все ошибки приложения одним except AppError.

Использование пользовательских исключений

def find_user(users, user_id):
    for user in users:
        if user["id"] == user_id:
            return user
    raise NotFoundError(f"Пользователь с id={user_id} не найден")

users = [{"id": 1, "name": "Алиса"}, {"id": 2, "name": "Борис"}]

try:
    user = find_user(users, 99)
except NotFoundError as e:
    print(f"Ошибка: {e}")   # Ошибка: Пользователь с id=99 не найден

Теперь вызывающий код может точно различить «не найдено» и другие ошибки.

Добавление атрибутов через init

Иногда нужно передать структурированные данные вместе с исключением — не только текстовое сообщение:

class ValidationError(Exception):
    def __init__(self, field, message):
        self.field = field
        self.message = message
        super().__init__(f"{field}: {message}")

def validate_age(age):
    if not isinstance(age, int):
        raise ValidationError("age", "должно быть целым числом")
    if not 0 <= age <= 120:
        raise ValidationError("age", f"должно быть от 0 до 120, получено {age}")

try:
    validate_age("двадцать пять")
except ValidationError as e:
    print(f"Поле: {e.field}")     # Поле: age
    print(f"Ошибка: {e.message}") # Ошибка: должно быть целым числом

super().__init__(message) — передаёт строку базовому классу Exception, чтобы str(e) и трейсбек показывали понятное сообщение.

Иерархия исключений

Грамотно спроектированная иерархия позволяет перехватывать ошибки на нужном уровне детализации:

class BookstoreError(Exception):
    """Базовое исключение книжного магазина."""

class BookNotFoundError(BookstoreError):
    pass

class OutOfStockError(BookstoreError):
    pass

class PaymentError(BookstoreError):
    pass

# Можно перехватить конкретное:
try:
    order(book_id=42)
except BookNotFoundError:
    print("Книга не существует")
except OutOfStockError:
    print("Книга закончилась")
except BookstoreError as e:
    print(f"Ошибка магазина: {e}")   # все остальные ошибки магазина

Соглашения по именованию

  • Имена заканчиваются на Error (ValidationError, NotFoundError).
  • Один базовый класс на библиотеку / модуль (AppError, BookstoreError).
  • Документируйте каждый класс: когда он выбрасывается и что означает.

Проверь себя

Что произойдёт при выполнении?

class MyError(Exception):
    pass

class SpecificError(MyError):
    pass

try:
    raise SpecificError("специфическая ошибка")
except MyError as e:
    print(f"Поймано: {e}")

SpecificError — наследник MyError, поэтому except MyError перехватит его. Вывод: Поймано: специфическая ошибка.

Итог

  • Собственные исключения создаются наследованием от Exception или его потомков.
  • pass в теле — минимальное исключение без дополнительной логики.
  • __init__ с дополнительными параметрами — структурированные данные об ошибке.
  • Иерархия: базовый класс модуля → конкретные ошибки.
  • Именование: суффикс Error; один базовый класс на библиотеку.

Модуль 11 завершён. В следующем модуле изучим объектно-ориентированное программирование: классы, объекты, методы и наследование.

Попробуйте интерактивную версию

Практические задачи, квизы и AI-наставник — бесплатный старт без карты

Перейти к практике