Как вызывать исключения в Python: raise и его возможности
Вызов исключения с помощью raise
Оператор raise в Python предназначен для принудительного возбуждения исключения. Основной и наиболее часто используемый способ - указать класс исключения и передать сообщение в виде строки:
raise ValueError("Передано недопустимое значение")Python raise (вызов исключения в python)
После выполнения этой строки программа немедленно прерывает нормальное выполнение, создаётся объект ValueError с заданным сообщением, и исключение передаётся по стеку вызовов. Если исключение не обработано, интерпретатор выводит traceback и завершает работу.
Распространённая ошибка - забыть указать сообщение или передать объект неправильного типа. Например, raise ValueError без скобок - это допустимо, но с пустым сообщением, что может усложнить отладку. Также нельзя выбрасывать исключение, не являющееся наследником BaseException (например, строку), - Python вызовет TypeError.
Как повторно выбросить уже перехваченное исключение?
Внутри блока except можно использовать raise без каких-либо аргументов. Это приводит к повторному возбуждению того же исключения, которое было перехвачено. Такой приём часто применяется, когда нужно выполнить какое-то действие (логирование, очистку), но после этого передать исключение дальше для обработки на более высоком уровне.
try:
risky_operation()
except ValueError:
log_error("Обнаружена ошибка ValueError")
raise # повторно выбрасывает то же исключение
Обратите внимание: при таком вызове сохраняется исходный traceback, что важно для отладки.
Если случайно написать raise с указанием класса, но без экземпляра, будет создано новое исключение, а не перевыброшено исходное. Это может исказить информацию об ошибке.
Как выбросить исключение класса без создания экземпляра?
Можно вызвать raise ValueError - в этом случае класс будет автоматически инстанциирован без аргументов. Результат эквивалентен raise ValueError(). Такой синтаксис удобен, если исключение не требует параметров или вызов с пустыми скобками выглядит избыточно.
raise KeyError
Однако стоит помнить, что некоторые классы исключений (например, пользовательские) могут ожидать обязательные аргументы в конструкторе - тогда такой вызов приведёт к ошибке во время создания исключения.
Как прикрепить к новому исключению исходную причину?
Конструкция raise ... from ... позволяет явно указать, что новое исключение было вызвано другим. Это особенно ценно при преобразовании одного типа ошибки в другой: цепочка сохраняется, и программист может проследить исходную причину.
try:
file = open("missing.txt")
except FileNotFoundError as e:
raise RuntimeError("Не удалось открыть файл") from e
В traceback появится строка "The above exception was the direct cause of the following exception".
Если использовать raise ... from None, цепочка подавляется. Это бывает нужно, когда внутренняя ошибка не должна быть видна пользователям (например, по соображениям безопасности). Однако делать это нужно осознанно, так как теряется диагностическая информация.
Как создать собственный класс исключения и выбросить его?
Пользовательские исключения обычно наследуют от Exception (или его подклассов). Затем его можно вызвать как обычное исключение:
class MyCustomError(Exception):
pass
raise MyCustomError("Произошла специфическая ошибка")
При необходимости в конструктор можно добавить дополнительные атрибуты для передачи контекста.
Если наследник определён с собственным __init__, нужно не забыть вызвать super().__init__(). Иначе сообщение может не сохраниться, что приведёт к пустому выводу при обработке.
Как передать в исключение несколько аргументов?
Стандартные исключения принимают только строку сообщения, но пользовательский класс может принимать произвольные аргументы. Например:
class ValidationError(Exception):
def __init__(self, field, value, message):
super().__init__(message)
self.field = field
self.value = value
raise ValidationError("email", "invalid@", "Некорректный формат")
Теперь в коде, перехватывающем исключение, можно получить доступ к атрибутам field и value.
Если переопределён __init__ и не вызван родительский, то при попытке распечатать исключение (print(e)) может отобразиться пустая строка, так как args не заполнен.
Можно ли выбросить готовый объект исключения?
Да, можно предварительно создать экземпляр и затем передать его в raise:
exc = ValueError("Ошибка была предсказуема")
raise exc
Это удобно, когда один и тот же объект нужно выбросить в разных местах или когда исключение конструируется сложным образом.
Обратите внимание, что после raise тот же объект может быть переброшен повторно только в том случае, если он не был изменён. Внутри блока except при повторном вызове raise без аргументов используется текущее перехваченное исключение, а не тот же объект извне.
Расширенные примеры использования raise
Пример 1: Многоуровневая цепочка исключений
В реальных приложениях часто возникает необходимость перехватить низкоуровневую ошибку и выбросить свою, сохранив всю цепочку. В этом примере имитируется работа с базой данных и кэшем.
class DatabaseError(Exception):
pass
class CacheError(Exception):
pass
def get_data(key):
try:
# имитация ошибки в БД
raise DatabaseError("Соединение потеряно")
except DatabaseError as e:
raise CacheError("Ошибка кэширования") from e
try:
get_data("user:123")
except CacheError as e:
print(f"Поймано: {e}")
print(f"Цепочка: {e.__cause__}")
Поймано: Ошибка кэширования Цепочка: Соединение потеряно
Благодаря from атрибут __cause__ содержит исходное исключение.
Пример 2: Пользовательское исключение с дополнительным поведением
Создадим исключение, которое при печати выводит не только сообщение, но и код ошибки.
class HTTPError(Exception):
def __init__(self, status_code, message):
super().__init__(f"[{status_code}] {message}")
self.status_code = status_code
try:
raise HTTPError(404, "Страница не найдена")
except HTTPError as e:
print(e) # [404] Страница не найдена
print(e.status_code) # 404
[404] Страница не найдена 404
Пример 3: raise в контекстном менеджере для замены исключения
Метод __exit__ может возвращать True, чтобы подавить исключение, или выбрасывать новое. Здесь показано, как заменить одно исключение другим.
class ErrorConverter:
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ValueError:
raise RuntimeError("Ошибка преобразована") from exc_val
return False # не подавляем другие исключения
with ErrorConverter():
raise ValueError("Исходная ошибка")
При выполнении будет выброшено RuntimeError с цепочкой от ValueError. Обратите внимание, что __exit__ не должен возвращать True в данном случае, иначе новое исключение не будет распространено.
Если в __exit__ возникнет своё исключение до того, как будет обработано исходное, то исходное может быть потеряно. Следует внимательно управлять цепочкой.
Пример 4: raise с динамическим именем класса
Иногда нужно выбросить исключение, класс которого определяется во время выполнения, например на основе конфигурации.
def raise_dynamic(error_type, message):
if error_type == "value":
cls = ValueError
elif error_type == "key":
cls = KeyError
else:
cls = RuntimeError
raise cls(message)
raise_dynamic("value", "Динамически созданное исключение")
Результат: будет выброшено ValueError с заданным сообщением.
Пример 5: Выброс системного исключения (KeyboardInterrupt)
Оператор raise позволяет сгенерировать не только ошибки, но и системные сигналы, такие как KeyboardInterrupt. Это может быть использовано для тестирования обработки прерываний.
import time
try:
raise KeyboardInterrupt("Тестовое прерывание")
except KeyboardInterrupt:
print("Прерывание обработано")
Прерывание обработано
Обычно KeyboardInterrupt генерируется нажатием Ctrl+C, но его можно вызвать и программно.