Как вызывать исключения в Python: raise и его возможности

Раздел: Основы Python -> Исключения и ошибки

Вызов исключения с помощью 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, но его можно вызвать и программно.

Вызов исключения в Python - comments

En
Python raise (python)