__cause__
или функции raise ... from ...
. Это позволяет отслеживать всю цепочку событий, приведших к ошибке.
try:
# ... что-то, что может вызвать ValueError
raise ValueError("Ошибка преобразования типа")
except ValueError as e:
try:
# ... обработка ValueError и возможно возникновение IOError
raise IOError("Ошибка ввода/вывода")
except IOError as e2:
raise RuntimeError("Критическая ошибка") from e2 # Связываем с IOError
except IOError as e3:
raise RuntimeError("Критическая ошибка (IOError)") from e3 # Связываем с IOError
RuntimeError
, атрибут __cause__
содержит оригинальный IOError
(или ValueError
, в зависимости от того, где была ошибка), позволяя провести детальный анализ причин ошибки. Использование raise ... from None
отключает цепочки исключений.
Для создания исключений с несколькими уровнями вложенности в Python, можно использовать механизм, когда одно исключение вызывает другое, сохраняя информацию о причине ошибки на каждом уровне. Это позволяет получить более подробную картину произошедшего и облегчает отладку сложных ситуаций.
Основной подход заключается в использовании аргумента from
в инструкции raise
. С помощью raise ExceptionA from ExceptionB
мы указываем, что ExceptionA
вызвано (или является прямым следствием) ExceptionB
.
Пример:
class DatabaseError(Exception):
"""Общий класс для ошибок базы данных."""
pass
class ConnectionError(DatabaseError):
"""Ошибка подключения к базе данных."""
pass
class QueryError(DatabaseError):
"""Ошибка выполнения запроса к базе данных."""
pass
def connect_to_database(host, port):
"""Попытка подключения к базе данных."""
try:
# Имитация неудачного подключения
raise OSError("Ошибка сети: невозможно подключиться к хосту")
except OSError as e:
raise ConnectionError("Не удалось установить соединение с базой данных") from e
def execute_query(connection, query):
"""Попытка выполнить запрос."""
try:
# Имитация ошибки в запросе
raise ValueError("Некорректный SQL запрос")
except ValueError as e:
raise QueryError("Ошибка при выполнении запроса") from e
def process_data(host, port, query):
"""Основная функция обработки данных."""
try:
connection = connect_to_database(host, port)
execute_query(connection, query)
except DatabaseError as e:
print(f"Произошла ошибка базы данных: {e}")
if e.__cause__:
print(f" Вызвано исключением: {e.__cause__}") #Выводит исходную ошибку
except Exception as e:
print(f"Непредвиденная ошибка: {e}")
# Пример использования
process_data("localhost", 5432, "SELECT * FROM invalid_table")
В данном примере:
DatabaseError
-> ConnectionError
, QueryError
.connect_to_database
ловит OSError
и генерирует ConnectionError
, указывая, что причиной была сетевая ошибка.execute_query
ловит ValueError
и генерирует QueryError
, указывая, что причиной была ошибка в запросе.process_data
обрабатывает исключение DatabaseError
и, если у него есть причина (__cause__
), выводит информацию о ней.Преимущества такого подхода:
DatabaseError
и повторить операцию, если причиной была временная сетевая проблема (ConnectionError
).Важно: При создании вложенных исключений следите за тем, чтобы каждое новое исключение предоставляло дополнительную информацию, а не просто повторяло предыдущее. Аргумент from
следует использовать только тогда, когда новое исключение действительно вызвано предыдущим.
Альтернативные подходы (встречаются реже):
В заключение, создание исключений с несколькими уровнями вложенности с помощью аргумента from
является мощным инструментом для обработки сложных ошибок в Python. Он обеспечивает сохранение цепочки причин, упрощает отладку и делает сообщения об ошибках более информативными.