Создание собственных исключений для контроля
В прошлом уроке мы научились ловить и обрабатывать стандартные ошибки Python с помощью try-except. Это мощный механизм, но иногда его не хватает. Что делать, если нужно сообщить о проблеме, которая не вписывается в существующие типы ошибок? Именно для таких случаев Python позволяет создавать свои исключения.
Зачем нужны свои исключения?
Представьте: вы пишете код для онлайн-магазина. Пользователь хочет купить товар, которого нет в наличии. Это не ошибка программы в привычном смысле (не синтаксис, не логика), но это исключительная ситуация, которую нужно обработать. Стандартные ValueError или TypeError не подходят для описания "товара нет на складе".
Свои исключения помогают:
- Сделать код понятнее. Имя исключения сразу говорит, что случилось.
- Разделить логику. Вы ловите конкретные ошибки, не затрагивая другие.
- Ускорить отладку. Понятные исключения быстро приводят к источнику проблемы.
- Строить иерархии. Вы можете создавать логическую структуру ошибок, наследуя их друг от друга.
Как создать своё исключение?
В Python все исключения — это классы, которые наследуются от базового класса Exception (или его подклассов, например, ValueError, TypeError). Чтобы создать своё исключение, просто объявите новый класс, который наследуется от Exception.
class InsufficientStockError(Exception):
"""
Исключение: товара нет на складе или его недостаточно.
"""
def __init__(self, item_name, requested_quantity, available_quantity):
self.item_name = item_name
self.requested_quantity = requested_quantity
self.available_quantity = available_quantity
message = (f"Недостаточно товара '{item_name}'. "
f"Запрошено: {requested_quantity}, в наличии: {available_quantity}.")
super().__init__(message) # Передаём сообщение родительскому классу
# Пример использования
def process_order(item, quantity):
# Допустим, это данные из базы данных
available_stock = {"Книга": 10, "Ручка": 5, "Тетрадь": 2}
if item not in available_stock:
raise ValueError(f"Товар '{item}' не найден.")
if quantity > available_stock[item]:
# Вызываем наше кастомное исключение
raise InsufficientStockError(item, quantity, available_stock[item])
print(f"Заказ на {quantity} шт. товара '{item}' успешно обработан.")
# Демонстрация работы
try:
process_order("Книга", 12) # Вызовет InsufficientStockError
except InsufficientStockError as e:
print(f"Ошибка заказа: {e}")
print(f"Детали: Товар '{e.item_name}', запрошено {e.requested_quantity}, в наличии {e.available_quantity}")
except ValueError as e:
print(f"Ошибка данных: {e}")
except Exception as e: # Ловим любые другие неожиданные ошибки
print(f"Произошла непредвиденная ошибка: {e}")
print("\n--- Второй пример ---")
try:
process_order("Ручка", 3) # Успешный заказ
process_order("Карандаш", 1) # Вызовет ValueError
except InsufficientStockError as e:
print(f"Ошибка заказа: {e}")
except ValueError as e:
print(f"Ошибка данных: {e}")
Что мы сделали в этом примере:
- Создали класс
InsufficientStockError, который наследуется отException. - В его конструкторе
__init__добавили параметры (item_name,requested_quantity,available_quantity), чтобы сообщение об ошибке было максимально информативным. - Вызвали конструктор родительского класса
super().__init__(message), чтобы передать ему стандартное сообщение об ошибке. - Функция
process_order"выбрасывает" (raise) наше кастомное исключение, если товара не хватает. - Блок
try-exceptловитInsufficientStockErrorиValueErrorпо отдельности, позволяя нам обрабатывать каждую ситуацию по-своему.
Важно: Всегда наследуйте свои исключения от
Exceptionили от более специфичных встроенных исключений, если это логично. Например, если ваше исключение связано с некорректными значениями, можно унаследоваться отValueError.
Иерархия исключений
Вы можете создавать целые "семейства" своих исключений, чтобы гибко управлять их обработкой. Например, можно создать базовое исключение для всех ошибок бизнес-логики, а от него уже наследовать более специфичные.
class BusinessLogicError(Exception):
"""Базовое исключение для всех ошибок бизнес-логики."""
pass
class InvalidInputError(BusinessLogicError):
"""Исключение: некорректный ввод данных."""
def __init__(self, message="Некорректный ввод данных", field=None):
super().__init__(message)
self.field = field
class PermissionDeniedError(BusinessLogicError):
"""Исключение: ошибка доступа."""
def __init__(self, user_role, required_role):
message = f"Доступ запрещен. Ваша роль: {user_role}, требуется: {required_role}."
super().__init__(message)
self.user_role = user_role
self.required_role = required_role
def check_access(user_role, resource):
if resource == "секретный_отчет" and user_role != "админ":
raise PermissionDeniedError(user_role, "админ")
print(f"Доступ к {resource} разрешен для {user_role}.")
def validate_data(data):
if not isinstance(data, dict):
raise InvalidInputError("Данные должны быть словарем.")
if "name" not in data or not data["name"]:
raise InvalidInputError("Поле 'name' обязательно.", field="name")
print("Данные валидны.")
try:
check_access("пользователь", "секретный_отчет")
except PermissionDeniedError as e:
print(f"Ошибка доступа: {e.message} (роль пользователя: {e.user_role})")
except BusinessLogicError as e: # Можно поймать любое исключение из нашей иерархии
print(f"Общая ошибка бизнес-логики: {e}")
print("\n--- Второй пример ---")
try:
validate_data({"age": 30})
except InvalidInputError as e:
print(f"Ошибка валидации: {e.message}")
if e.field:
print(f"Проблема в поле: {e.field}")
except BusinessLogicError as e:
print(f"Общая ошибка бизнес-логики: {e}")
Здесь мы можем поймать PermissionDeniedError или InvalidInputError по отдельности. А можем поймать их все разом, перехватив BusinessLogicError, так как они являются его подклассами. Это даёт большую гибкость в обработке ошибок.
Создание своих исключений — важный шаг к написанию чистого, понятного и легко поддерживаемого кода. Они помогают чётко разграничить разные типы проблем и эффективно на них реагировать.
В следующем разделе мы перейдём к одной из ключевых тем для любого разработчика — основам работы с системой контроля версий Git. Без этого инструмента невозможно представить современную командную разработку.