Python «поднять» без аргументов: что такое «последнее исключение, которое было активным в текущей области»? - PullRequest
10 голосов
/ 03 июня 2019

Документация Python гласит:

Если выражения отсутствуют, raise повторно вызывает последнее исключение, которое было активным в текущей области.

(Python 3: https://docs.python.org/3/reference/simple_stmts.html#raise; Python 2.7: https://docs.python.org/2.7/reference/simple_stmts.html#raise.)

Однако понятие «последний активный», похоже, изменилось. Посмотрите следующий пример кода:

#
from __future__ import print_function
import sys
print('Python version =', sys.version)

try:
    raise Exception('EXPECTED')
except:
    try:
        raise Exception('UNEXPECTED')
    except:
        pass
    raise # re-raises UNEXPECTED for Python 2, and re-raises EXPECTED for Python 3

, что приводит к чему-то, чего я не ожидал с Python 2:

Python version = 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)]
Traceback (most recent call last):
  File "./x", line 10, in <module>
    raise Exception('UNEXPECTED')
Exception: UNEXPECTED

но имеет ожидаемый (мной) результат с Python 3:

Python version = 3.6.8 (default, Feb 14 2019, 22:09:48)
[GCC 7.4.0]
Traceback (most recent call last):
  File "./x", line 7, in <module>
    raise Exception('EXPECTED')
Exception: EXPECTED

и

Python version = 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)]
Traceback (most recent call last):
  File "./x", line 7, in <module>
    raise Exception('EXPECTED')
Exception: EXPECTED

Так что значит "последний ... активный"? Есть ли какая-то документация по этому критическому изменению? Или это ошибка Python 2?

И что еще более важно: как лучше всего сделать эту работу в Python 2? (Желательно, чтобы код продолжал работать в Python 3.)


Обратите внимание , что если кто-то изменит код на

#
from __future__ import print_function
import sys
print('Python version =', sys.version)

def f():
    try:
        raise Exception('UNEXPECTED')
    except:
        pass

try:
    raise Exception('EXPECTED')
except:
    f()
    raise # always raises EXPECTED

тогда все начинает работать и для Python 2:

Python version = 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)]
Traceback (most recent call last):
  File "./x", line 13, in <module>
    raise Exception('EXPECTED')
Exception: EXPECTED

Я собираюсь переключиться на это ...

Ответы [ 2 ]

4 голосов
/ 04 июня 2019

Поведение Python 2 - это не столько ошибка , сколько ошибка дизайна . Это было исправлено в Python 3.0 путем добавления функций цепочки исключений. Самое близкое к документации об этом изменении можно найти в PEP 3134 - цепочка исключений и встроенные трассировки мотивация:

Во время обработки одного исключения (исключение A) возможно возникновение другого исключения (исключение B). В сегодняшнем Python (версия 2.4), если это происходит, исключение B распространяется наружу, а исключение A теряется.

Это именно то, что вы видите в 2.7: ОЖИДАЕМЫЙ (A) был потерян, потому что НЕОЖИДАННЫЙ (B) появился и перезаписал его. Благодаря новым возможностям цепочки исключений в Python 3 полный контекст обеих ошибок может быть сохранен с помощью атрибутов __cause__ и __context__ в экземплярах исключений.

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

try:
    raise Exception('EXPECTED')
except Exception as err_expected:
    try:
        raise Exception('UNEXPECTED')
    except Exception as err_unexpected:
        pass
    raise err_expected

Если вы хотите подавить функцию объединения исключений перекрестно-совместимым способом, вы можете сделать это, установив err_expected.__cause__ = None перед повторным поднятием.

0 голосов
/ 04 июня 2019

raise использует ту же информацию, что и sys.exc_info, которая документирует оба поведения . Поскольку поведение каждого кадра, которое использует ваш обходной путь, задокументировано, это путь.

PEP 3110 внес несколько изменений в оператор except. Я полагаю, что он включал и этот, но единственное, что явно упоминалось, это то, что исключение, хранимое as, отбрасывается при выходе из except.

...