Разница между "рейзом" и "повышением e"?
В python существует ли разница между raise
и raise e
в исключающем блоке?
dis
показывает мне разные результаты, но я не знаю, что это значит.
Какое конечное поведение обоих?
import dis
def a():
try:
raise Exception()
except Exception as e:
raise
def b():
try:
raise Exception()
except Exception as e:
raise e
dis.dis(a)
# OUT: 4 0 SETUP_EXCEPT 13 (to 16)
# OUT: 5 3 LOAD_GLOBAL 0 (Exception)
# OUT: 6 CALL_FUNCTION 0
# OUT: 9 RAISE_VARARGS 1
# OUT: 12 POP_BLOCK
# OUT: 13 JUMP_FORWARD 22 (to 38)
# OUT: 6 >> 16 DUP_TOP
# OUT: 17 LOAD_GLOBAL 0 (Exception)
# OUT: 20 COMPARE_OP 10 (exception match)
# OUT: 23 POP_JUMP_IF_FALSE 37
# OUT: 26 POP_TOP
# OUT: 27 STORE_FAST 0 (e)
# OUT: 30 POP_TOP
# OUT: 7 31 RAISE_VARARGS 0
# OUT: 34 JUMP_FORWARD 1 (to 38)
# OUT: >> 37 END_FINALLY
# OUT: >> 38 LOAD_CONST 0 (None)
# OUT: 41 RETURN_VALUE
dis.dis(b)
# OUT: 4 0 SETUP_EXCEPT 13 (to 16)
# OUT: 5 3 LOAD_GLOBAL 0 (Exception)
# OUT: 6 CALL_FUNCTION 0
# OUT: 9 RAISE_VARARGS 1
# OUT: 12 POP_BLOCK
# OUT: 13 JUMP_FORWARD 25 (to 41)
# OUT: 6 >> 16 DUP_TOP
# OUT: 17 LOAD_GLOBAL 0 (Exception)
# OUT: 20 COMPARE_OP 10 (exception match)
# OUT: 23 POP_JUMP_IF_FALSE 40
# OUT: 26 POP_TOP
# OUT: 27 STORE_FAST 0 (e)
# OUT: 30 POP_TOP
# OUT: 7 31 LOAD_FAST 0 (e)
# OUT: 34 RAISE_VARARGS 1
# OUT: 37 JUMP_FORWARD 1 (to 41)
# OUT: >> 40 END_FINALLY
# OUT: >> 41 LOAD_CONST 0 (None)
# OUT: 44 RETURN_VALUE
Ответы
Ответ 1
В этом случае нет никакой разницы. raise
без аргументов всегда будет поднимать последнее исключенное значение (которое также доступно с помощью sys.exc_info()
).
Причина, по которой байт-код отличается, заключается в том, что Python является динамическим языком, и интерпретатор действительно не знает, что e
относится к (немодифицированному) исключению, которое в настоящее время обрабатывается. Но это не всегда так, считают:
try:
raise Exception()
except Exception as e:
if foo():
e = OtherException()
raise e
Что такое e
сейчас? Невозможно сказать при компиляции байт-кода (только при фактическом запуске программы).
В простых примерах, подобных вашим, может быть возможно, чтобы интерпретатор Python "оптимизировал" байт-код, но пока никто этого не сделал. И почему они должны? Это микро-оптимизация в лучшем случае и может все еще ломаться тонкими способами в неясных условиях. Существует много других фруктов, которые висят намного ниже этого и более питательны для загрузки; -)
Ответ 2
Существует различие в обратных трассах, которые генерируются двумя формами.
Используя raise
, этот код:
try:
int("hello")
except ValueError as e:
raise
Дает следующую обратную трассировку:
Traceback (most recent call last):
File "myfile.py", line 2, in <module>
int("hello")
ValueError: invalid literal for int() with base 10: 'hello'
Используя raise e
следующим образом:
try:
int("hello")
except ValueError as e:
raise e
Дает следующую обратную трассировку
Traceback (most recent call last):
File "myfile.py", line 4, in <module>
raise e
ValueError: invalid literal for int() with base 10: 'hello'
Разница в том, что в случае raise
правильная строка, ссылающаяся на исходный источник исключения, цитируется в обратном направлении, но в случае raise e
трассировка ссылается на строку raise e
не на исходную причину.
Поэтому я рекомендую всегда использовать raise
, а не raise e
.
Ответ 3
Можно очистить "последнее исключение" (т.е. результат sys.exc_info()
) info с помощью sys.exc_clear()
. Например, это произойдет, если блок catch вызывает функцию foo()
, которая сама имеет специальную обработку ошибок.
В этом случае raise
с аргументом и без него будет означать разные вещи. raise e
все равно будет ссылаться на исключение, пойманное несколько строк выше, а стенограмма raise
будет пытаться поднять None
, что является ошибкой.