Додавання інформації до винятку?


142

Я хочу досягти чогось такого:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

Але я отримую:

Traceback..
  IOError('Stuff')

Будь-які підказки, як цього досягти? Як це зробити як в Python 2, так і 3?


Шукаючи документацію для messageатрибуту «Виняток», я знайшов це питання ТА , BaseException.message застарілий у Python 2.6 , який, схоже, вказує на його використання зараз не рекомендується (і чому його немає в документах).
мартіно

на жаль, схоже, це посилання вже не працює.
Майкл Скотт Катберт

1
@MichaelScottCuthbert ось хороша альтернатива: itmaybeahack.com/book/python-2.6/html/p02/…
Niels Keurentjes

Ось справді вдале пояснення того, що таке статус атрибута повідомлення та його відношення до атрибута args та PEP 352 . Це з безкоштовної книги « Будівництво навичок на Python » Стівена Ф. Лотта.
мартіно

Відповіді:


118

Я б це зробив так, щоб змінити його тип foo()не вимагатиме також його зміни bar().

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

Оновлення 1

Ось невелика модифікація, яка зберігає оригінальний трекбек:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

Оновлення 2

Для Python 3.x код у моєму першому оновлення синтаксично невірний плюс ідея наявності messageатрибуту BaseExceptionбуло відкликано у зміні на PEP 352 на 2012-05-16 (моє перше оновлення було розміщено на 2012-03-12) . Тому в Python 3.5.2 в будь-якому разі, вам потрібно буде щось зробити за цими лініями, щоб зберегти прослідкування і не жорсткий код типу виключення у функції bar(). Також зауважте, що буде рядок:

During handling of the above exception, another exception occurred:

у відображених повідомленнях відстеження.

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

Оновлення 3

Коментатор запитав, чи є спосіб , який буде працювати як в Python 2 і 3. Незважаючи на те, що відповідь може здатися, що «Ні» з - за відмінності синтаксису, то є спосіб обійти це, використовуючи допоміжну функцію як reraise()в sixAdd- на модулі. Отже, якщо ви хочете скористатися бібліотекою з якихось причин, нижче - спрощена окрема версія.

Зауважте також, що оскільки виняток повторно збільшується в межах reraise()функції, це з’явиться в будь-якому порушеному прослідкуванні, але кінцевим результатом є те, що ви хочете.

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')

3
Це втрачає зворотній шлях, який-небудь пошкоджує точку додавання інформації до існуючого винятку. Крім того, він не працює винятками з ctor, який бере> 1 аргумент (тип - це те, що ви не можете контролювати з місця, де ви ловите виняток).
Вацлав Славік

1
@ Вацлав: Досить легко запобігти втраті задніх часів - як показано в доданому оновленням. Незважаючи на те, що це все ще не справляється з кожним можливим винятком, воно працює в випадках, подібних до того, що було показано в питанні про ОП.
мартіно

1
Це не зовсім правильно. Якщо відміни типу (e) __str__, ви можете отримати небажані результати. Також зауважте, що другий аргумент передається конструктору, заданому першим аргументом, що дає дещо безглузде type(e)(type(e)(e.message). По-третє, напр. Повідомлення припинено на користь e.args [0].
bukzor

1
Отже, не існує портативного способу, який працює в Python 2 і 3?
Еліас Дорнелес

1
@martineau Яка мета імпорту всередину, крім блоку? Це для збереження пам'яті, лише імпортуючи при необхідності?
AllTradesJack

115

Якщо ви приїхали сюди, шукаючи рішення для Python 3, інструкція говорить:

Піднімаючи новий виняток (замість того, щоб використовувати голий raiseдля повторного підняття винятку, який зараз обробляється), неявний контекст винятку може бути доповнений явною причиною, використовуючи команду from з підвищенням:

raise new_exc from original_exc

Приклад:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

Як виглядає так у підсумку:

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

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

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

Перетворення повністю недискрипту TypeErrorв приємне повідомлення з натяками на рішення, не псуючи оригінальний виняток.


14
Це найкраще рішення, оскільки отримане виняток вказує на первісну причину, надайте більше деталей.
JT

Чи є якесь рішення, до якого ми можемо додати якесь повідомлення, але все ж не створити новий виняток? Я маю на увазі просто розширити повідомлення екземпляра винятку.
edcSam

Яа ~~ це працює, але це відчуває себе чимось, чого мені не слід робити. Повідомлення зберігається в e.args, але це кортеж, тому його неможливо змінити. Тож спочатку скопіюйте argsу список, потім модифікуйте його, а потім скопіюйте його як кортеж:args = list(e.args) args[0] = 'bar' e.args = tuple(args)
Кріс

27

Якщо припустити, що ви не хочете або не можете змінювати foo (), ви можете зробити це:

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

Це дійсно єдине рішення, яке вирішує проблему в Python 3 без негарного та заплутаного повідомлення "Під час обробки вищевказаного винятку стався інший виняток".

У випадку, якщо до сліду стека буде додано рядок повторного підйому, написання raise eзамість raiseвиконає трюк.


але в цьому випадку, якщо виняток змінюється в foo, я повинен також змінити смугу.
anijhaw

1
Якщо ви виберете Exception (відредагований вище), ви можете зловити будь-яку стандартну бібліотечну виняток (як і ті, що успадковують від Exception і викликають Exception .__ init__).
Стів Ховард

6
щоб бути більш повною / e.args = ('mynewstr' + e.args[0],) + e.args[1:]
спільною

1
@ nmz787 Це найкраще рішення для Python 3 насправді. Яка саме ваша помилка?
Крістіан

1
@Dubslow та martineau Я включив ваші пропозиції до редагування.
Крістіан

9

Мені не подобаються всі відповіді на даний момент. Вони все ще надто багатослівні. Як у коді, так і в повідомленні.

Все, що я хочу мати, - це стек-трек, що вказує на вихідне виключення, між ними не виняток, тому немає створення нових винятків, просто повторне підняття оригіналу з усіма відповідними станами кадру стека в ньому, що привело туди.

Стів Ховард дав приємну відповідь, яку я хочу розширити, ні, зменшити ... лише до python 3.

except Exception as e:
    e.args = ("Some failure state", *e.args)
    raise

Єдине нове - розширення / розпакування параметрів, що робить його невеликим і досить простим у використанні.

Спробуй це:

foo = None

try:
    try:
        state = "bar"
        foo.append(state)

    except Exception as e:
        e.args = ("Appending '"+state+"' failed", *e.args)
        raise

    print(foo[0]) # would raise too

except Exception as e:
    e.args = ("print(foo) failed: " + str(foo), *e.args)
    raise

Це дасть вам:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")

Простий симпатичний принт може бути чимось на кшталт

print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))

5

Один зручний підхід, який я використав, - це використовувати атрибут класу як сховище для деталей, оскільки атрибут класу доступний як з об’єкта класу, так і з екземпляра класу:

class CustomError(Exception):
    def __init__(self, details: Dict):
        self.details = details

Потім у своєму коді:

raise CustomError({'data': 5})

А під час виявлення помилки:

except CustomError as e:
    # Do whatever you want with the exception instance
    print(e.details)

Не дуже корисно, оскільки ОП вимагає, щоб деталі були надруковані як частина сліду стека, коли оригінальний виняток буде викинуто та не зафіксовано.
ковбер

Я думаю, що рішення добре. Але опис не відповідає дійсності. Атрибути класу копіюються в випадки, коли ви їх створюєте. Отже, коли ви змінюєте атрибут "деталі", наприклад, атрибут класу все ще буде "None". У будь-якому випадку ми хочемо, щоб тут була така поведінка.
Адам Валлнер

2

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

Я все одно хотів би знайти додаткове вдосконалення, яке не змінює тип.

from contextlib import contextmanager
@contextmanager
def helpful_info():
    try:
        yield
    except Exception as e:
        class CloneException(Exception): pass
        CloneException.__name__ = type(e).__name__
        CloneException.__module___ = type(e).__module__
        helpful_message = '%s\n\nhelpful info!' % e
        import sys
        raise CloneException, helpful_message, sys.exc_traceback


class BadException(Exception):
    def __str__(self):
        return 'wat.'

with helpful_info():
    raise BadException('fooooo')

Оригінальний трекбек та тип (назва) зберігаються.

Traceback (most recent call last):
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "re_raise.py", line 5, in helpful_info
    yield
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
__main__.BadException: wat.

helpful info!

2

Я надаю фрагмент коду, який я часто використовую, коли хочу додати додаткову інформацію до виключення. Я працюю і в Python 2.7 та 3.6.

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

Код вище призводить до наступного виводу:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers


Я знаю, що це трохи відхиляється від прикладу, наведеного у питанні, але все ж сподіваюся, що хтось вважає це корисним.


1

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

Наприклад:

class MyError(Exception):
   def __init__(self, value):
     self.value = value
     Exception.__init__(self)

   def __str__(self):
     return repr(self.value)

2
Не потребує адреси щось змінювати / додавати до messageоригінального винятку (але, думаю, це може бути виправлено).
мартіно

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