Подтвердить что ты не робот

Получение исключений, когда исключение уже присутствует в Python 3

Что произойдет с моим первым исключением (A), когда второй (B) поднят в следующем коде?

class A(Exception): pass
class B(Exception): pass

try:
    try:
        raise A('first')
    finally:
        raise B('second')
except X as c:
    print(c)

Если вы используете X = A, я получаю:

Traceback (most recent call last):
  File "raising_more_exceptions.py", line 6, in 
    raise A('first')
__main__.A: first

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "raising_more_exceptions.py", line 8, in 
    raise B('second')
__main__.B: second

Но если X = B, я получаю:

second

Вопросы

  • Где мое первое исключение отправилось?
  • Почему только внешнее исключение доступно?
  • Как отключить внешнее исключение и сделать ререйз более ранними исключениями?

Update0

В этом вопросе конкретно рассматривается Python 3, поскольку его обработка исключений сильно отличается от Python 2.

4b9b3361

Ответ 1

Обработка исключений Pythons будет обрабатывать только одно исключение за раз. Однако объекты исключений подчиняются тем же правилам переменной и сборку мусора, что и все остальное. Следовательно, если вы сохраните объект исключения в переменной где-нибудь, вы можете иметь дело с ним позже, даже если возникает другое исключение.

В вашем случае, когда возникает исключение во время выражения "finally", Python 3 будет распечатывать трассировку первого исключения до второго исключения, чтобы быть более полезным.

Более распространенным случаем является то, что вы хотите создать исключение во время явной обработки исключений. Затем вы можете "сохранить" исключение в следующем исключении. Просто передайте его как параметр:

>>> class A(Exception):
...     pass
... 
>>> class B(Exception):
...     pass
... 
>>> try:
...     try:
...         raise A('first')
...     except A as e:
...         raise B('second', e)
... except Exception as c:
...     print(c.args[1])
... 
first

Как вы видите, теперь вы можете получить доступ к исходному исключению.

Ответ 2

Исключение "cause" доступно как c.__ context__ в вашем последнем обработчике исключений. Python использует эту информацию для рендеринга более полезной трассировки. В Python 2.x исходное исключение было бы потеряно, это только для Python 3.

Обычно вы должны использовать это, чтобы вызывать непротиворечивое исключение, сохраняя при этом исходное исключение доступным (хотя довольно круто, что это происходит автоматически из обработчика исключений, я этого не знал!):

try:
    do_something_involving_http()
except (URLError, socket.timeout) as ex:
    raise MyError('Network error') from ex

Дополнительная информация (и некоторые другие полезные вещи, которые вы можете сделать) здесь: http://docs.python.org/3.3/library/exceptions.html

Ответ 3

  • Он был выброшен.
  • Только одно исключение может быть "активным" за раз в потоке.
  • Вы не можете, если вы каким-либо образом инкапсулируете предыдущее исключение в последующее исключение.

Ответ 4

Я считаю, что все ингредиенты, которые отвечают на ваш вопрос (-ы), уже находятся в существующих ответах. Позвольте мне объединиться и разработать.

Позвольте мне повторить ваш код вопроса, чтобы указать ссылки на номера строк:

 1  class A(Exception): pass
 2  class B(Exception): pass
 3 
 4  try:
 5      try:
 6          raise A('first')
 7      finally:
 8          raise B('second')
 9  except X as c:
10      print(c)

Итак, чтобы ответить на ваши вопросы:

  • Где мое первое исключение отправилось?

Ваше первое исключение A возникает в строке 6. Предложение finally в строке 7 всегда выполняется, как только блок try (строки 5-6) оставлен, независимо от того, оставлен ли он из-за успешное завершение или из-за повышенного исключения. Пока выполняется предложение finally, строка 8 вызывает другое исключение B. Как указывали Леннарт и Игназио, можно отслеживать только одно исключение, которое в последнее время поднимается. Таким образом, как только B поднимается, общий блок try (строки 4-8) завершается, а исключение B захватывается оператором except в строке 9, если оно соответствует (if X B).

  1. Почему только внешнее исключение доступно?

Надеюсь, теперь это ясно из моего объяснения 1. Вы могли бы поймать внутреннее/нижнее/первое исключение. Чтобы слить в Lennart ответ, слегка измененный, вот как поймать оба:

class A(Exception): pass
class B(Exception): pass
try:
    try:
        raise A('first')
    except A as e:
        raise B('second', e)
except Exception as c:
    print(c)

Вывод:

('second', A('first',))
  1. Как отключить внешнее исключение и сделать ререйз более ранними исключениями?

В примере Леннарта решение этого вопроса - это строка except A as e, где внутреннее/нижнее/первое исключение захватывается и сохраняется в переменной e.

Как общее ощущение, когда нужно ловить исключения, когда игнорировать их, а когда повторно поднимать, может быть этот вопрос и ответить Alex Martelli.

Ответ 5

Отвечая на вопрос 3, вы можете использовать:

raise B('second') from None

Будет удалено исключение A traceback.

Traceback (most recent call last):
  File "raising_more_exceptions.py", line 8, in 
    raise B('second')
__main__.B: second