Как мне вызвать такое же исключение с помощью настраиваемого сообщения в Python?


153

У меня tryв коде есть такой блок:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

Строго говоря, я на самом деле поднимаю другого ValueError , а не ValueErrorброшенного do_something...(), как errв данном случае. Как прикрепить собственное сообщение к err? Я пробую следующий код, но не могу из-за того err, что ValueError экземпляр не может быть вызван:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)

13
@Hamish, добавление дополнительной информации и повторное создание исключений могут быть очень полезны при отладке.
Йохан Лундберг

@Johan Совершенно верно - и для этого нужна трассировка стека. Не совсем понимаю, почему вы отредактировали существующее сообщение об ошибке вместо того, чтобы выдавать новую ошибку.
Хэмиш,

@ Хамиш. Конечно, но вы можете добавить другие вещи. На свой вопрос посмотрите мой ответ и пример UnicodeDecodeError. Если у вас есть комментарии по этому поводу, возможно, вместо этого прокомментируйте мой ответ.
Johan Lundberg


1
@Kit - это 2020 год, а Python 3 повсюду. Почему бы не изменить принятый ответ на ответ Бена :-)
митинг

Ответы:


95

Обновление: для Python 3 проверьте ответ Бена


Чтобы прикрепить сообщение к текущему исключению и повторно поднять его: (внешняя попытка / исключение предназначена только для демонстрации эффекта)

Для python 2.x, где x> = 6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

Это также будет правильным, если errбудет производным от ValueError. Например UnicodeDecodeError.

Обратите внимание, что вы можете добавить все, что захотите err. Например err.problematic_array=[1,2,3].


Изменить: @Ducan указывает в комментарии, указанном выше, не работает с python 3, поскольку .messageне является членом ValueError. Вместо этого вы можете использовать это (действительный python 2.6 или новее или 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Edit2:

В зависимости от цели вы также можете добавить дополнительную информацию под своим именем переменной. Для python2 и python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info

9
Поскольку вы приложили усилия для использования обработки исключений в стиле Python 3 и print, вероятно, вы должны заметить, что ваш код не работает в Python 3.x, поскольку messageдля исключений нет атрибута. err.args = (err.args[0] + " hello",) + err.args[1:]может работать более надежно (а затем просто преобразовать в строку, чтобы получить сообщение).
Дункан

1
К сожалению, нет гарантии, что args [0] является строковым типом, представляющим сообщение об ошибке - «Кортеж аргументов, переданный конструктору исключения. Некоторые встроенные исключения (например, IOError) ожидают определенного количества аргументов и придают особое значение элементы этого кортежа, в то время как другие обычно вызываются только с одной строкой, дающей сообщение об ошибке. ". Таким образом, код не будет работать. Arg [0] не является сообщением об ошибке (это может быть int или строка, представляющая имя файла).
Трент

1
@Taras, Интересно. У вас есть ссылка на это? Затем я бы добавил к совершенно новому члену: err.my_own_extra_info. Или инкапсулируйте все это в моем собственном исключении, сохраняя новую и исходную информацию.
Йохан Лундберг

2
Реальный пример того, когда args [0] не является сообщением об ошибке - docs.python.org/2/library/exceptions.html - "exception EnvironmentError" Базовый класс для исключений, которые могут возникать вне системы Python: IOError, OSError. Когда исключения этого типа создаются с двумя кортежами, первый элемент доступен в атрибуте errno экземпляра (предполагается, что это номер ошибки), а второй элемент доступен в атрибуте strerror (обычно это связанный сообщение об ошибке). Сам кортеж также доступен в атрибуте args. "
Трент

2
Я вообще этого не понимаю. Единственная причина, по которой установка .messageатрибута здесь что-то делает, заключается в том, что этот атрибут печатается явно . Если бы вы вызывали исключение без перехвата и печати, вы бы не увидели, что .messageатрибут делает что-нибудь полезное.
DanielSank

183

Если вам посчастливилось поддерживать только python 3.x, это действительно становится прекрасным :)

поднять с

Мы можем связать исключения с помощью метода raise from .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

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

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

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

with_traceback

Или вы можете использовать with_traceback .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

При использовании этой формы исключение, которое может перехватить ваш вызывающий абонент, имеет обратную связь, откуда произошла исходная ошибка.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

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


2
Можно ли добавить собственное сообщение к исключению без дополнительной трассировки? Например, можно raise Exception('Smelly socks') from eизменить, чтобы просто добавить «Вонючие носки» в качестве комментария к исходной трассировке, а не вводить новую собственную трассировку.
joelostblom 06

Такое поведение вы получите из ответа Йохана Лундберга
Бен,

4
это действительно прекрасно. Спасибо.
aljabear

5
Повторное создание нового исключения или цепочки исключений с новыми сообщениями во многих случаях создает больше путаницы, чем необходимо. Сами по себе исключения сложно обрабатывать. Лучшая стратегия - просто добавить ваше сообщение к аргументу исходного исключения, если возможно, как в err.args + = ("message",), и повторно вызвать сообщение исключения. Функция трассировки может не привести вас к номерам строк, в которых было обнаружено исключение, но точно перенесет вас туда, где возникло исключение.
user-asterix

2
Вы также можете явно подавить отображение цепочки исключений, указав None в предложении from:raise RuntimeError("Something bad happened") from None
pfabri

9
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

печатает:

There is a problem: invalid literal for int() with base 10: 'a'

2
Мне было интересно, есть ли идиома Python для того, что я пытаюсь сделать, кроме создания другого экземпляра.
Kit

@Kit - я бы назвал это «повторным возбуждением исключения»: docs.python.org/reference/simple_stmts.html#raise
eumiro

1
@eumiro, нет вы делаете новое исключение. Смотрите мой ответ. Из вашей ссылки: «... но повышение без выражений должно быть предпочтительным, если исключение, которое должно быть повторно возбуждено, было последним активным исключением в текущей области».
Йохан Лундберг

3
@JohanLundberg - raiseбез параметров ререйз . Если OP хочет добавить сообщение, он должен создать новое исключение и может повторно использовать сообщение / тип исходного исключения.
eumiro 06

2
Если вы хотите добавить сообщение, вы не можете создать новое сообщение с нуля, выбрасывая «ValueError». Тем самым вы уничтожаете базовую информацию о том, что это за ValueError (аналогично нарезке в C ++). К повторной бросанием в то же исключение с подъемным без аргумента, вы передаете исходный объект с таким правильным типом конкретного (производный от ValueError).
Йохан Лундберг,

9

Похоже, все ответы добавляют информацию в e.args [0], тем самым изменяя существующее сообщение об ошибке. Есть ли недостаток в расширении кортежа args вместо этого? Я думаю, что возможный плюс в том, что вы можете оставить исходное сообщение об ошибке в покое в тех случаях, когда требуется синтаксический анализ этой строки; и вы можете добавить несколько элементов в кортеж, если ваша настраиваемая обработка ошибок вызвала несколько сообщений или кодов ошибок, для случаев, когда трассировка будет анализироваться программно (например, с помощью инструмента системного мониторинга).

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

или

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

Вы видите обратную сторону такого подхода?


Мой старый ответ не изменяет e.args [0].
Йохан Лундберг

4

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

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")

4
Это не сохраняет трассировку стека.
участок

Запрашивающий не указывает, что трассировка стека должна быть сохранена.
shrewmouse

Не ведите себя намеренно. Первоначальный вопрос дословно звучал так: «Как мне задать то же самое Exceptionс помощью специального сообщения в Python?» Этот отсутствие ответа вызывает новое исключение и, таким образом, вообще не дает ответа на исходный вопрос. Вот почему у нас не может быть хороших вещей.
Сесил Карри,

4

Это функция, которую я использую для изменения сообщения об исключении в Python 2.7 и 3.x с сохранением исходной трассировки. Это требуетsix

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)

Это один из немногих ответов, который действительно отвечает на исходный вопрос. Так что это хорошо. К сожалению, Python 2.7 (и, следовательно, six) является EOL. Итак, это плохо. Хотя технически можно будет использовать и sixв 2020 году, в этом нет никаких ощутимых преимуществ. Решения Pure-Python 3.x сейчас намного предпочтительнее.
Сесил Карри,

2

Текущий ответ не сработал для меня, если исключение не перехвачено повторно, добавленное сообщение не отображается.

Но выполнение приведенных ниже действий сохраняет трассировку и отображает добавленное сообщение независимо от того, перехватывается ли исключение или нет.

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

(Я использовал Python 2.7, не пробовал в Python 3)


2

Встроенные исключения Python 3 имеют strerrorполе:

except ValueError as err:
  err.strerror = "New error message"
  raise err

Похоже, это не работает. Вы что-то упускаете?
MasayoMusic

2
strerrorПеременная экземпляра является специфическим для OSErrorисключения . Поскольку для большинства ValueErrorисключений эта переменная гарантированно не определяется, это нерешение обычно вызывает исключения, нечитаемые человеком, и, таким образом, является активно вредным. лол, братан.
Сесил Карри

2

Либо вызовите новое исключение с сообщением об ошибке, используя

raise Exception('your error message')

или

raise ValueError('your error message')

в том месте, где вы хотите его поднять, ИЛИ прикрепить (заменить) сообщение об ошибке в текущее исключение, используя 'from' (поддерживается только Python 3.x):

except ValueError as e:
  raise ValueError('your message') from e

Спасибо, @gberger, подход 'from e' на самом деле не поддерживается python 2.x
Алексей Антоненко

1
Неудивительно, что это не дает ответа на вопрос.
Сесил Карри,

2

Это работает только с Python 3 . Вы можете изменить исходные аргументы исключения и добавить свои собственные.

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

В функции reraiseмы добавляем к исходным аргументам исключения любые новые аргументы, которые нам нужны (например, сообщение). Наконец, мы повторно вызываем исключение, сохраняя историю трассировки.

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the expection and preserve the traceback info so thta we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

выход

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')

1
Безусловно, лучший ответ. Это единственный ответ, который отвечает на исходный вопрос, сохраняет исходную трассировку и является чистым Python 3.x. Безумный реквизит за то, что еще и стук в этот «очень, очень плохой» мем-барабан. Юмор, несомненно, хорошая вещь, особенно в таких сухих, технических ответах, как этот. Браво!
Сесил Карри,

0

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

Это сработало для меня:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)

-4

если вы хотите настроить тип ошибки, вы можете просто определить класс ошибки на основе ValueError.


как это поможет в этом случае?
Йохан Лундберг

... представьте, что вы действительно думаете, что это правильный ответ.
Сесил Карри,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.