Як повторно викликати виняток у вкладених блоках try / Osim?


107

Я знаю, що якщо я хочу повторно викликати виняток, я просто використовую raiseбез аргументів у відповідному exceptблоці. Але з урахуванням вкладеного виразу типу

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # I'd like to raise the SomeError as if plan_B()
                 # didn't raise the AlsoFailsError

як я можу повторно підняти, SomeErrorне порушуючи трасування стека? raiseсам би в цьому випадку повторно підняв останні AlsoFailsError. Або як я міг зробити рефакторинг коду, щоб уникнути цієї проблеми?


2
Ви пробували ввести plan_Bіншу функцію, яка повертає Trueуспіх і Falseвиняток? Тоді зовнішній exceptблок міг бути простоif not try_plan_B(): raise
Дрю Макгоуен

@DrewMcGowen На жаль, більш реалістичним є той факт, що це всередині функції, яка приймає довільні об'єкти, argі я спробував би зателефонувати, arg.plan_B()що може викликати AttributeErrorчерез argне надання плану Б
Тобіас Кінцлер

Погляньте на модуль відстеження
Пако

@Paco Дякую, буду (хоча відповідь уже показує простіший шлях)
Тобіас Кінцлер

@DrewMcGowen Я написав відповідь на основі вашого коментаря , який виглядає менш пітонічним, ніж відповідь користувача4815162342 . Але це пов’язано з моїм бажанням також мати повернене значення і дозволяючи plan_Bпідняти винятки
Тобіас Кінцлер

Відповіді:


128

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

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # or raise e from None - see below

Вироблений відстеження включатиме додаткове повідомлення, яке SomeErrorсталося під час обробки AlsoFailsError(через raise eперебування всередині except AlsoFailsError). Це вводить в оману, оскільки те, що насправді сталося, відбувається навпаки - ми зіткнулися AlsoFailsErrorі впорались із цим, намагаючись оговтатися SomeError. Щоб отримати зворотний зв'язок, який не включає AlsoFailsError, замініть raise eна raise e from None.

У Python 2 ви б зберігали тип винятку, значення та зворотний зв'язок у локальних змінних та використовували форму з трьома аргументамиraise :

try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        raise t, v, tb

Чудово, ось що я тут теж знайшов , дякую! Хоча пропозиція є raise self.exc_info[1], None, self.exc_info[2]після self.exc_info = sys.exc_info()- [1]з якоїсь причини поставити на перше місце
Тобіас Кінцлер

3
@TobiasKienzler raise t, None, tbвтратить значення винятку і змусить raiseповторно створити його з типу, надаючи вам менш конкретне (або просто неправильне) значення винятку. Наприклад, якщо викликане виняток є KeyError("some-key"), воно просто повторно підніме KeyError()та опустить точний відсутній ключ із зворотного відстеження.
користувач4815162342

3
@TobiasKienzler Ще слід мати можливість висловити це в Python 3 як raise v.with_traceback(tb). (У вашому коментарі навіть сказано стільки, за винятком того, що він пропонує відновити екземпляр значення.)
user4815162342

2
Крім того, червоне попередження не зберігати sys.exc_info()в локальній змінній мало сенс до Python 2.0 (випущене 13 років тому), але сьогодні межує із смішним. Сучасний Python був би майже марним без колектора циклів, оскільки кожна нетривіальна бібліотека Python створює цикли без паузи і залежить від їх правильного очищення.
користувач4815162342

1
@ user4815162342 Ви можете знищити вкладену помилку "сталася інша помилка", написавши "підняти e з None".
Маттіас Урліхс,

19

Навіть якщо прийняте рішення є правильним, добре вказати на бібліотеку Six, яка має рішення Python 2 + 3, використовуючи six.reraise.

шість. ререйз ( exc_type , exc_value , exc_traceback = Немає)

Збільште виняток, можливо, з іншим відстеженням. [...]

Отже, ви можете написати:

import six


try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        six.reraise(t, v, tb)

1
Хороший момент - говорячи про шість, ви також можете використовувати, six.raise_fromякщо хочете включити інформацію, яка plan_B()також не вдалася.
Тобіас Кінцлер,

1
@TobiasKienzler: Я вважаю, що це інше використання: коли six.raise_fromви створюєте новий виняток, який пов’язаний з попереднім, ви не піднімаєте повторне підвищення , тому зворотне відстеження відрізняється.
Laurent LAPORTE

1
Я точно кажу: якщо у вас reraiseсклалося враження, що something()викинуте SomeError, якщо raise_fromви також знаєте, що це призвело plan_B()до страти, але кидання AlsoFailsError. Тож це залежить від використання. Я думаю raise_from, що полегшить налагодження
Тобіас Кінцлер,

9

Відповідно до пропозиції Дрю Макгоуена , але, дбаючи про загальний випадок (де sприсутнє повертане значення ), ось альтернатива відповіді користувача 4815162342 :

try:
    s = something()
except SomeError as e:
    def wrapped_plan_B():
        try:
            return False, plan_B()
        except:
            return True, None
    failed, s = wrapped_plan_B()
    if failed:
        raise


2
@ user4815162342 Хороший момент :) Хоча тим часом у Python3 я б розглянув raise from, тому трасування стека також дозволить мені побачити план B невдалим. Який можна емулювати в Python 2 до речі.
Тобіас Кінцлер

5

Python 3.5+ все одно приєднує інформацію про трасування до помилки, тому більше не потрібно зберігати її окремо.

>>> def f():
...   try:
...     raise SyntaxError
...   except Exception as e:
...     err = e
...     try:
...       raise AttributeError
...     except Exception as e1:
...       raise err from None
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in f
  File "<stdin>", line 3, in f
SyntaxError: None
>>> 

2
Питання в іншому винятку, що відбувся під час except. Але ви маєте рацію, коли я замінюю err = e, скажімо raise AttributeError,, спочатку ви отримуєте SyntaxErrorтрасування стека, за яким слідують a During handling of the above exception, another exception occurred:і AttributeErrorтрасування стека. Корисно знати, хоча, на жаль, не можна покладатися на встановлення 3,5+. PS: ff verstehen nicht-Deutsche vermutlich nicht;)
Тобіас Кінцлер

Добре, тому я змінив приклад, щоб викликати ще один виняток, який (як і запитувалося вихідне запитання) ігнорується, коли я повторно піднімаю перший.
Маттіас Урлікс

3
@TobiasKienzler 3.5+ (на що я його змінив), схоже, є загальновизнаним форматом. Чи був denkst du? ;)
linusg

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