Я пишу модуль и хочу иметь единую иерархию исключений для исключений, которые он может вызывать (например, наследование от FooError
абстрактного класса для всех foo
особых исключений модуля). Это позволяет пользователям модуля перехватывать эти конкретные исключения и при необходимости обрабатывать их отдельно. Но многие исключения, вызванные модулем, возникают из-за другого исключения; например, сбой при выполнении какой-либо задачи из-за ошибки OSError в файле.
Мне нужно «обернуть» перехваченное исключение таким образом, чтобы оно имело другой тип и сообщение , чтобы информация была доступна дальше по иерархии распространения тем, что перехватывает исключение. Но я не хочу терять существующий тип, сообщение и трассировку стека; это вся полезная информация для тех, кто пытается устранить проблему. Обработчик исключений верхнего уровня бесполезен, поскольку я пытаюсь украсить исключение до того, как оно продвинется дальше по стеку распространения, а обработчик верхнего уровня опаздывает.
Это частично решается путем получения foo
конкретных типов исключений моего модуля из существующего типа (например, class FooPermissionError(OSError, FooError)
), но это не упрощает перенос существующего экземпляра исключения в новый тип или изменение сообщения.
В Python PEP 3134 «Цепочка исключений и встроенная трассировка» обсуждается изменение, принятое в Python 3.0 для «цепочки» объектов исключений, чтобы указать, что новое исключение было вызвано во время обработки существующего исключения.
То, что я пытаюсь сделать, связано: мне нужно, чтобы он работал и в более ранних версиях Python, и мне он нужен не для связывания, а только для полиморфизма. Как правильно это сделать?
источник
except Exception as e
->raise type(e), type(e)(e.message + custom_message), sys.exc_info()[2]
-> это решение из другого вопроса SO . Это некрасиво, но функционально.Ответы:
Python 3 представил цепочку исключений (как описано в PEP 3134 ). Это позволяет при возникновении исключения ссылаться на существующее исключение как на «причину»:
try: frobnicate() except KeyError as exc: raise ValueError("Bad grape") from exc
exc
Пойманное исключение ( , KeyError) таким образом становится частью (является «причиной») нового исключения - ValueError. «Причина» доступна для любого кода, улавливающего новое исключение.При использовании этой функции
__cause__
атрибут устанавливается. Встроенный обработчик исключений также знает, как сообщить о «причине» и «контексте» исключения вместе с трассировкой.В Python 2 , похоже, этот вариант использования не имеет хорошего ответа (как описано Яном Бикингом и Недом Батчелдером ). Облом.
источник
try: return 2 / 0 except ZeroDivisionError as e: raise ValueError(e)
Вы можете использовать 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. Я бы не рекомендовал это делать.
источник
sys.exc_info()
@Devin. В нем говорится: «Назначение возвращаемого значения трассировки локальной переменной в функции, которая обрабатывает исключение, вызовет циклическую ссылку». Однако в следующем примечании говорится, что, начиная с Python 2.2, цикл можно очистить, но более эффективно просто избегать этого.Вы можете создать свой собственный тип исключения, который расширяет любое обнаруженное вами исключение .
class NewException(CaughtException): def __init__(self, caught): self.caught = caught try: ... except CaughtException as e: ... raise NewException(e)
Но в большинстве случаев я думаю, что было бы проще поймать исключение, обработать его и либо
raise
исходное исключение (и сохранить трассировку), либоraise NewException()
. Если бы я вызвал ваш код и получил одно из ваших пользовательских исключений, я бы ожидал, что ваш код уже обработал любое исключение, которое вам нужно было перехватить. Таким образом, мне не нужно обращаться к нему самому.Изменить: я нашел этот анализ способов выбросить собственное исключение и сохранить исходное исключение. Нет хороших решений.
источник
Я также обнаружил, что много раз мне нужно «обернуть» возникшие ошибки.
Это входит как в область видимости функции, так и иногда включает только некоторые строки внутри функции.
Создана оболочка для использования
decorator
иcontext manager
:Реализация
import inspect from contextlib import contextmanager, ContextDecorator import functools class wrap_exceptions(ContextDecorator): def __init__(self, wrapper_exc, *wrapped_exc): self.wrapper_exc = wrapper_exc self.wrapped_exc = wrapped_exc def __enter__(self): pass def __exit__(self, exc_type, exc_val, exc_tb): if not exc_type: return try: raise exc_val except self.wrapped_exc: raise self.wrapper_exc from exc_val def __gen_wrapper(self, f, *args, **kwargs): with self: for res in f(*args, **kwargs): yield res def __call__(self, f): @functools.wraps(f) def wrapper(*args, **kw): with self: if inspect.isgeneratorfunction(f): return self.__gen_wrapper(f, *args, **kw) else: return f(*args, **kw) return wrapper
Примеры использования
декоратор
@wrap_exceptions(MyError, IndexError) def do(): pass
при звонке
do
метода не беспокойтесьIndexError
, простоMyError
try: do() except MyError as my_err: pass # handle error
менеджер контекста
def do2(): print('do2') with wrap_exceptions(MyError, IndexError): do()
внутри
do2
, вcontext manager
, еслиIndexError
поднят, он будет завернут и поднятMyError
источник
Самым простым решением ваших задач должно быть следующее:
try: upload(file_id) except Exception as upload_error: error_msg = "Your upload failed! File: " + file_id raise RuntimeError(error_msg, upload_error)
Таким образом, вы можете позже распечатать свое сообщение и конкретную ошибку, выданную функцией загрузки.
источник