Як підняти ту саму Виняток із користувацьким повідомленням у Python?


145

Я маю цей 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 Абсолютно - і ось для чого це стек-трек. Не можу зрозуміти, чому ви редагували існуюче повідомлення про помилку замість того, щоб створювати нову помилку.
Гаміш

@Hamish. Звичайно, але ви можете додати інші речі. Що стосується вашого запитання, подивіться на мою відповідь та приклад UnicodeDecodeError. Якщо у вас є коментарі до цього, можливо, замість цього прокоментуйте мою відповідь.
Йохан Лундберг


1
@Kit це 2020 рік, а python 3 є скрізь. Чому б не змінити прийняту відповідь на відповідь Бена :-)
міс

Відповіді:


88

Оновлення: Для 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 - "виняток EnvironmentError Базовий клас для винятків, які можуть траплятися поза системою Python: IOError, OSError. Коли винятки цього типу створюються з двома кортежами, перший елемент доступний в атрибуті errno екземпляра (передбачається, що це номер помилки), а другий елемент доступний в атрибуті strerror (зазвичай це асоційований повідомлення про помилку). Сам кортеж також доступний в атрибуті args. "
Трент

2
Я взагалі цього не розумію. Єдина причина, що налаштування .messageатрибуту робить щось тут - це те, що цей атрибут явно друкується. Якби ви збільшили виняток без лову та друку, ви не побачили, щоб .messageатрибут робив щось корисне.
DanielSank

171

Якщо вам пощастило підтримувати лише python 3.x, це справді стає справою краси :)

підняти з

Ми можемо пов'язати винятки, використовуючи рейз від .

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

Зауважте, нижній виняток містить рядок, де ми виконували недійсний поділ, а також рядок, де ми повторно оцінюємо виняток.


1
Чи можна додати користувальницьке повідомлення до винятку без додаткового простеження? Наприклад, можна raise Exception('Smelly socks') from eзмінити, щоб просто додати "Смішні шкарпетки" як коментар до оригінального трекбека, а не вводити новий власний слід.
joelostblom

Така поведінка ви отримаєте з відповіді Йохана Лундберга
Бен

3
це справді прекрасно. Дякую.
чорниця

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

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

10
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'

1
Мені було цікаво, чи є ідіома Python для того, що я намагаюся зробити, окрім підняття іншої інстанції.
Кіт

@Kit - я б назвав це "повторним винятком": docs.python.org/reference/simple_stmts.html#raise
eumiro

1
@eumiro, Ні, ви робите новий виняток. Дивіться мою відповідь. З вашого посилання: "... але перевагу без виразів слід віддавати перевагу, якщо виняток, який потрібно повторно підняти, був останнім часом активним винятком у поточному діапазоні."
Йохан Лундберг

3
@JohanLundberg - raiseбез параметрів відновлюється. Якщо ОП хоче додати повідомлення, він повинен створити новий виняток і може повторно використовувати повідомлення / тип вихідного винятку.
eumiro

2
Якщо ви хочете додати повідомлення, ви не можете створити нове повідомлення з нуля, кинувши "ValueError". Тим самим ви знищуєте основну інформацію про те, що це за ValueError (подібний до нарізки в C ++). До повторної киданням в той же виняток з підйомним без аргументу, ви передаєте вихідний об'єкт з таким правильним типом конкретного (похідний від ValueError).
Йохан Лундберг

9

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

## 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")

3
Це не зберігає слід стека.
plok

Питувач не вказує, чи слід зберегти слід стека.
кривошик

4

Або підніміть новий виняток, використовуючи повідомлення про помилку

raise Exception('your error message')

або

raise ValueError('your error message')

в тому місці, де ви хочете підняти його АБО приєднати (замінити) повідомлення про помилку в поточний виняток, використовуючи "з" (підтримується лише Python 3.x):

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

Thanx, @gberger, підхід "від e" насправді не підтримується python 2.x
Олексій Антоненко

3

Це функція, яку я використовую, щоб змінювати повідомлення про виключення в 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)

3

У вбудованих винятках Python 3 є strerrorполе:

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

Це, здається, не працює. Ви пропускаєте щось?
MasayoMusic

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)


1

Жодне з перерахованих вище рішень не робило саме те, що я хотів, а саме - додати деяку інформацію до першої частини повідомлення про помилку, тобто я хотів, щоб мої користувачі спочатку бачили своє власне повідомлення.

Це працювало для мене:

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)

0

Це працює лише з 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')

-3

якщо ви хочете налаштувати тип помилки, ви можете просто визначити клас помилки на основі ValueError.


як би це допомогло в цьому випадку?
Йохан Лундберг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.