Свои классы исключений
Свои классы исключений
Встроенные исключения (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 завершён. В следующем модуле изучим объектно-ориентированное программирование: классы, объекты, методы и наследование.