Як підняти ту саму Виняток із користувацьким повідомленням у 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.