Возобновить исключение с другим типом и сообщением, сохраняя существующую информацию - PullRequest
110 голосов
/ 30 марта 2009

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

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

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

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

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

Ответы [ 5 ]

152 голосов
/ 27 апреля 2009

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

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

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

При использовании этой функции устанавливается атрибут __cause__. Встроенный обработчик исключений также знает, как сообщать «причину» и «контекст» исключения вместе с трассировкой.


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

34 голосов
/ 30 марта 2009

Вы можете использовать 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

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

10 голосов
/ 30 марта 2009

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

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

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

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

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

1 голос
/ 29 декабря 2018

Я также обнаружил, что во многих случаях мне нужно немного «обернуть» ошибки.

Это включается как в область действия функции, так и иногда включает только некоторые строки внутри функции.

Создана оболочка для использования 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

0 голосов
/ 17 декабря 2015

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...