Difference between "raise" and "raise e"?

14,248

Solution 1

There is no difference in this case. raise without arguments will always raise the last exception thrown (which is also accessible with sys.exc_info()).

The reason the bytecode is different is because Python is a dynamic language and the interpreter doesn't really "know" that e refers to the (unmodified) exception that is currently being handled. But this may not always be the case, consider:

try:
    raise Exception()
except Exception as e:
    if foo():
        e = OtherException()
    raise e

What is e now? There is no way to tell when compiling the bytecode (only when actually running the program).

In simple examples like yours, it might be possible for the Python interpreter to "optimize" the bytecode, but so far no one has done this. And why should they? It's a micro-optimization at best and may still break in subtle ways in obscure conditions. There is a lot of other fruit that is hanging a lot lower than this and is more nutritious to boot ;-)

Solution 2

There is a difference in the backtraces that the two forms generate.

Using raise, this code:

try:
   int("hello")
except ValueError as e:
   raise

Gives the following backtrace:

Traceback (most recent call last):
  File "myfile.py", line 2, in <module>
    int("hello")
ValueError: invalid literal for int() with base 10: 'hello'

Using raise e as follows:

try:
   int("hello")
except ValueError as e:
   raise e

Gives the following backtrace

Traceback (most recent call last):
  File "myfile.py", line 4, in <module>
    raise e
ValueError: invalid literal for int() with base 10: 'hello'

The difference is that in the raise case, the correct line referencing the original source of the exception is quoted in the backtrace, but in the raise e case the traceback references the raise e line not the original cause.

Therefore, I recommend always using raise rather than raise e.

Solution 3

It is possible to clear the "last exception" (i.e. the result of sys.exc_info()) info with sys.exc_clear(). For instance, this would happen if the catch block called a function foo(), which itself has special error handling.

In that case, raise with and without an argument would mean diffent things. raise e would still have a reference to the exception caught a few lines above, while the raise shorthand would try to raise None, which is an error.

Share:
14,248

Related videos on Youtube

Daenyth
Author by

Daenyth

Professional computer nerd

Updated on September 14, 2022

Comments

  • Daenyth
    Daenyth over 1 year

    In python, is there a difference between raise and raise e in an except block?

    dis is showing me different results, but I don't know what it means.

    What's the end behavior of both?

    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        
    
    • Jérôme
      Jérôme about 8 years
      Possible duplicate of raise with no argument
    • Daenyth
      Daenyth about 8 years
      @Jérôme I don't think this is a duplicate of that. This question is about the difference between no-arg and with-arg versions in this specific code sample. That question is about how no-arg works generally. They're related but not duplicate
    • Jérôme
      Jérôme about 8 years
      Yes, and the accepted answer is interesting precisely for this reason. If there is a way to unflag a question, I'll do it.
  • Mark Amery
    Mark Amery over 6 years
    The behaviour you're describing is true of Python 2, but not Python 3. In Python 3, raise e does not swallow the original source and actually shows strictly more information in this case.
  • Carson Ip
    Carson Ip over 5 years
    This is a very important point. I've spent way too much time to trace the real source of error due to raise e.