Ответ 1
Вернитесь назад, чтобы ответить. Начну с того, что не отвечу на ваш вопрос.: -)
Это действительно работает?
def f():
try:
raise Exception('bananas!')
except:
pass
raise
Итак, что делает вышеизложенное? Cue Jeopardy music.
Хорошо, тогда карандаши вниз.
# python 3.3
4 except:
5 pass
----> 6 raise
7
RuntimeError: No active exception to reraise
# python 2.7
1 def f():
2 try:
----> 3 raise Exception('bananas!')
4 except:
5 pass
Exception: bananas!
Хорошо, это было плодотворно. Для удовольствия попробуйте назвать исключение.
def f():
try:
raise Exception('bananas!')
except Exception as e:
pass
raise e
Что теперь?
# python 3.3
4 except Exception as e:
5 pass
----> 6 raise e
7
UnboundLocalError: local variable 'e' referenced before assignment
# python 2.7
4 except Exception as e:
5 pass
----> 6 raise e
7
Exception: bananas!
Семантика исключений сильно изменилась между python 2 и 3. Но если поведение python 2 для вас вообще удивительно, подумайте: в основном это соответствует тому, что питон делает везде.
try:
1/0
except Exception as e:
x=4
#can I access `x` here after the exception block? How about `e`?
try
и except
не являются областями. На самом деле мало что происходит на питоне; у нас есть "правило LEGB", чтобы запомнить четыре пространства имен: "Локальные", "Закрывающиеся", "Глобальные", "Встроенные". Другие блоки просто не являются областями; Я могу с радостью объявить x
в цикле for
и ожидать, что все еще сможет ссылаться на него после этого цикла.
Итак, неудобно. Должны ли исключения быть закрытыми для закрытого лексического блока? Python 2 говорит нет, python 3 говорит yes. Но я упрощаю вещи здесь; Голый raise
- это то, о чем вы сначала спросили, и проблемы тесно связаны, но на самом деле не совпадают. Python 3 мог бы утверждать, что именованные исключения привязаны к их блоку, не обращаясь к голой теме raise
.
Что горит raise
do‽
Общее использование - использовать bare raise
как средство сохранения трассировки стека. Поймать, выполнить регистрацию/очистку, ререйз. Прохладный, мой код очистки не отображается в traceback, работает 99,9% времени. Но все может идти на юг, когда мы пытаемся обрабатывать вложенные исключения в обработчике исключений. Иногда. (см. примеры внизу, когда это/не проблема)
Интуитивно, no-argument raise
будет правильно обрабатывать вложенные обработчики исключений и вычислять правильное "текущее" исключение для ререйза. Однако это не совсем реальность. Оказывается, что - вхождение в подробности реализации здесь - информация об исключении сохраняется как член текущего объекта фрейма. И в python 2 просто нет сантехники для обработки push-popping-обработчиков исключений в стеке в пределах одного кадра; просто просто поле, содержащее последнее исключение, независимо от любой обработки, которую мы могли бы сделать с ней. То, что горит raise
.
6,9. Операция raise
raise_stmt ::= "raise" [expression ["," expression ["," expression]]]
Если выражения не присутствуют, рейз повторно вызывает последнее исключение, был активен в текущей области.
Итак, да, это проблема глубоко внутри python 2, связанная с тем, как хранится информация о трассировке - в традиции Highlander может быть только один объект трассировки, сохраненный в данном стеке стека. Как следствие, голый raise
ререйзит, который, по мнению текущего кадра, является "последним" исключением, что не обязательно является тем, которое, по нашему мнению, является человеческим мозгом, является специфическим для лексически-вложенного блока исключений, на котором мы находимся время. Ба, области!
Итак, исправлено в python 3?
Да. Как? Новая инструкция байткода (две, фактически, есть еще одна неявная в начале исключения обработчиков), но на самом деле кто заботится - все это "просто работает" интуитивно, Вместо получения RuntimeError: error from cleanup
ваш примерный код поднимает RuntimeError: error from throws
, как ожидалось.
Я не могу дать вам официальную причину, почему это не было включено в python 2. Проблема была известна с PEP 344, которая упоминает Раймонда Хеттингера, поднимающего проблему в 2003 году. Если бы я должен был догадаться, исправление этого является нарушающим изменением (среди прочего, это влияет на семантику sys.exc_info
), и это часто является достаточно хорошей причиной, чтобы не делать этого в незначительном выпуск.
Параметры, если вы находитесь на python 2:
1) Назовите исключение, которое вы планируете ререйзировать, и просто обработайте строку или две, которые будут добавлены в конец вашей трассировки стека. Ваш пример nested
будет выглядеть следующим образом:
def nested():
try:
throws()
except BaseException as e:
try:
cleanup()
except:
pass
raise e
И связанная трассировка:
Traceback (most recent call last):
File "example", line 24, in main
nested()
File "example", line 17, in nested
raise e
RuntimeError: error from throws
Итак, трассировка изменена, но она работает.
1.5) Используйте версию с тремя аргументами raise
. Многие люди не знают об этом, и это законный (неуклюжий) способ сохранить трассировку стека.
def nested():
try:
throws()
except:
e = sys.exc_info()
try:
cleanup()
except:
pass
raise e[0],e[1],e[2]
sys.exc_info
дает нам 3-кортеж, содержащий (тип, значение, трассировку), что и делает 3-аргументная версия raise
. Обратите внимание, что этот синтаксис 3-arg работает только в python 2.
2) Рефакторинг вашего кода очистки таким образом, чтобы он не мог вызывать необработанное исключение. Помните, что все о областях - переместите try/except
из nested
и в свою собственную функцию.
def nested():
try:
throws()
except:
cleanup()
raise
def cleanup():
try:
cleanup_code_that_totally_could_raise_an_exception()
except:
pass
def cleanup_code_that_totally_could_raise_an_exception():
raise RuntimeError('error from cleanup')
Теперь вам не нужно беспокоиться; поскольку исключение никогда не попадало в область nested
, оно не будет мешать исключению, которое вы планируете делать повторно.
3) Используйте голый raise
, как вы делали, прежде чем читать все это и жить с ним; код очистки обычно не вызывает исключений, правильно?: -)