Исключить повторное возбуждение с другим типом и сообщением, сохраняя существующую информацию

Я пишу модуль и хочу иметь единую иерархию исключений для исключений, которые он может поднять (например, наследовать от абстрактного класса FooError для всех исключений модуля foo). Это позволяет пользователям модуля улавливать те особые исключения и обрабатывать их отчетливо, если это необходимо. Но многие из исключений, поднятых из модуля, возникают из-за какого-то другого исключения; например при выполнении какой-либо задачи из-за OSError в файле.

Мне нужно "обернуть" исключение, пойманное таким образом, чтобы оно имело другой тип и сообщение, чтобы информация была доступна в дальнейшем по иерархии распространения, тем самым улавливая исключение. Но я не хочу потерять существующий тип, сообщение и трассировку стека; что вся полезная информация для кого-то пытается отладить проблему. Обработчик исключений верхнего уровня не подходит, поскольку я пытаюсь украсить исключение, прежде чем он продвигается дальше вверх по стеку распространения, а обработчик верхнего уровня слишком поздно.

Это частично решается путем получения моего модуля foo конкретных типов исключений из существующего типа (например, class FooPermissionError(OSError, FooError)), но это не упрощает перенос существующего экземпляра исключения в новый тип и не изменяет сообщение.

Python PEP 3134 "Цепочки исключений и встроенные трассировки" обсуждает изменение, принятое в Python 3.0 для "цепочки" объектов исключений, чтобы указать, что во время обработки существующего исключения было создано новое исключение.

То, что я пытаюсь сделать, связано: мне нужно, чтобы он работал и в более ранних версиях Python, и мне это нужно не для цепочки, а только для полиморфизма. Каков правильный способ сделать это?

Ответ 1

Python 3 представил цепочку исключений (как описано в PEP 3134). Это позволяет создавать исключение, ссылаясь на существующее исключение как на "причину":

try:
    frobnicate()
except KeyError as exc:
    raise ValueError("Bad grape") from exc

Исключенное исключение, таким образом, становится частью (является "причиной" ) нового исключения и доступно для любого кода, который ловит новое исключение.


В Python 2, похоже, этот прецедент не имеет хорошего ответа (как описано Ian Bicking и Ned Batchelder). Облом.

Ответ 2

Вы можете использовать sys.exc_info(), чтобы получить трассировку, и поднять новое исключение с указанной трассировкой (как упоминает PEP). Если вы хотите сохранить старый тип и сообщение, вы можете сделать это в случае исключения, но это полезно только в том случае, если вы его поймаете.

Например

import sys

def failure():
    try: 1/0
    except ZeroDivisionError, e:
        type, value, traceback = sys.exc_info()
        raise ValueError, ("You did something wrong!", type, value), traceback

Конечно, это действительно не так полезно. Если бы это было так, нам не понадобился бы этот PEP. Я бы не рекомендовал это делать.

Ответ 3

Вы можете создать свой собственный тип исключения, который расширяет какое исключение вы поймали.

class NewException(CaughtException):
    def __init__(self, caught):
        self.caught = caught

try:
    ...
except CaughtException as e:
    ...
    raise NewException(e)

Но большую часть времени, я думаю, было бы проще поймать исключение, обработать его и либо raise исходное исключение (и сохранить трассировку), либо raise NewException(). Если бы я вызывал ваш код, и я получил одно из ваших особых исключений, я бы ожидал, что ваш код уже обработал все исключения, которые вы должны были поймать. Таким образом, мне не нужно обращаться к нему самостоятельно.

Изменить: я нашел этот анализ способов бросить свое собственное исключение и сохранить исходное исключение. Нет хороших решений.

Ответ 4

Самое сложное решение для ваших нужд должно быть следующим:

try:
     upload(file_id)
except Exception as upload_error:
     error_msg = "Your upload failed! File: " + file_id
     raise RuntimeError(error_msg, upload_error)

Таким образом, вы можете позже распечатать свое сообщение и конкретную ошибку, выгруженную функцией загрузки