Разница между "рейзом" и "повышением 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, что является ошибкой.