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


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.