Как создать несколько уровней иерархии исключений для различных ситуаций?

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


class CustomError(Exception):
    """Базовый класс для пользовательских исключений."""
    pass

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

class NetworkError(CustomError):
    """Ошибка сети."""
    pass

class ConnectionError(NetworkError):
    """Ошибка соединения."""
    pass

try:
    # Код, который может вызвать исключение
    raise ConnectionError("Не удалось установить соединение.")
except ConnectionError as e:
    print(f"Произошла ошибка соединения: {e}")
except NetworkError as e:
    print(f"Произошла сетевая ошибка: {e}")
except CustomError as e:
    print(f"Произошла пользовательская ошибка: {e}")
except Exception as e:
    print(f"Произошла непредвиденная ошибка: {e}")
  

В примере выше: `CustomError` - базовый класс, `ValidationError` и `NetworkError` - его наследники. `ConnectionError` наследуется от `NetworkError`, создавая второй уровень иерархии. Обработчики исключений позволяют ловить исключения на разных уровнях иерархии.


Чтобы создать несколько уровней иерархии исключений в Python для различных ситуаций, необходимо создать пользовательские классы исключений, наследуя их от базового класса Exception или от его подклассов (например, ValueError, TypeError, IOError). Это позволяет организовать исключения логически и обрабатывать их более гранулярно.

Вот пример:


<!-- Пример кода Python -->
class ApplicationError(Exception):
    """Базовый класс для всех исключений приложения."""
    pass


class ConfigurationError(ApplicationError):
    """Исключения, связанные с конфигурацией."""
    pass


class MissingConfiguration(ConfigurationError):
    """Конфигурация отсутствует."""
    def __init__(self, setting_name):
        self.setting_name = setting_name
        super().__init__(f"Не найдена конфигурация: {setting_name}")


class InvalidConfiguration(ConfigurationError):
    """Конфигурация недействительна."""
    def __init__(self, setting_name, reason):
        self.setting_name = setting_name
        self.reason = reason
        super().__init__(f"Недействительная конфигурация {setting_name}: {reason}")


class DataAccessError(ApplicationError):
    """Исключения, связанные с доступом к данным."""
    pass


class DatabaseConnectionError(DataAccessError):
    """Ошибка подключения к базе данных."""
    pass


class RecordNotFoundError(DataAccessError):
    """Запись не найдена."""
    def __init__(self, record_id):
        self.record_id = record_id
        super().__init__(f"Запись с ID {record_id} не найдена")


# Пример использования
def load_config(setting_name):
    # Имитация логики загрузки конфигурации
    if setting_name == "database_url":
        raise MissingConfiguration(setting_name)
    elif setting_name == "api_key":
        raise InvalidConfiguration(setting_name, "Key is too short")
    else:
        return "Some Value"


def get_data_from_db(record_id):
    # Имитация логики доступа к базе данных
    if record_id == 123:
        raise DatabaseConnectionError("Failed to connect to database")
    elif record_id == 456:
        raise RecordNotFoundError(record_id)
    else:
        return {"id": record_id, "data": "Some data"}


try:
    # load_config("database_url") # Вызовет MissingConfiguration
    config_value = load_config("some_setting")
    print(f"Config value: {config_value}")


except MissingConfiguration as e:
    print(f"Ошибка конфигурации: {e}")
except InvalidConfiguration as e:
    print(f"Ошибка конфигурации: {e}")
except ConfigurationError as e: # перехватывает все ConfigurationError
    print(f"Общая ошибка конфигурации: {e}")

try:
    get_data_from_db(456)
except DatabaseConnectionError as e:
    print(f"Ошибка подключения к БД: {e}")
except RecordNotFoundError as e:
    print(f"Запись не найдена: {e}")
except DataAccessError as e: # перехватывает все DataAccessError
    print(f"Общая ошибка доступа к данным: {e}")

except ApplicationError as e: # Перехватит все ApplicationError
    print(f"Общая ошибка приложения: {e}")

except Exception as e: # Ловит все не обработанные исключения.
    print(f"Непредвиденная ошибка: {type(e).__name__}, {e}")
  

Основные моменты:

  • Базовый класс: Начинаем с базового класса исключений, обычно ApplicationError, который наследуется от Exception. Этот класс является корнем нашей иерархии.
  • Промежуточные классы: Создаем промежуточные классы (например, ConfigurationError, DataAccessError) для группировки связанных исключений.
  • Конкретные классы: Определяем конкретные классы исключений (например, MissingConfiguration, InvalidConfiguration, DatabaseConnectionError, RecordNotFoundError), которые наследуются от соответствующих промежуточных классов.
  • Пользовательские конструкторы: Добавляем пользовательские конструкторы (__init__) для передачи дополнительной информации об ошибке, такой как имя отсутствующей настройки или ID записи.
  • Обработка исключений: Используем блоки try...except для перехвата исключений на разных уровнях иерархии. Важно располагать блоки except от наиболее конкретных к наиболее общим, чтобы гарантировать, что конкретные исключения будут обработаны в первую очередь.
  • Порядок перехвата: Порядок блоков except имеет значение. Если первым идет except ApplicationError, он перехватит *все* исключения, наследованные от него, и последующие блоки except для более конкретных исключений никогда не будут выполнены.
  • Класс Exception: Важно в конце разместить except Exception as e: чтобы перехватить все необработанные исключения.

Преимущества такой иерархии:

  • Гранулярная обработка: Позволяет обрабатывать конкретные типы ошибок по-разному.
  • Легкость понимания: Делает код более читаемым и понятным, так как исключения организованы логически.
  • Гибкость: Позволяет добавлять новые типы исключений без изменения существующего кода обработки ошибок.
  • Улучшенная отладка: Упрощает отладку, так как легче определить, какой тип ошибки произошел.

Например, можно перехватить все ConfigurationError и предпринять общие действия (например, повторить попытку загрузки конфигурации), или перехватить конкретное MissingConfiguration и вывести пользователю сообщение об отсутствии необходимой настройки.

0