5

What does raise do, if it's not inside a try or except clause, but simply as the last statement in the function?

def foo(self):
    try:
        # some code that raises an exception
    except Exception as e:
        pass

    # notice that the "raise" is outside
    raise

This example prints 1 but not 2 so it must be that the last raise statement simply raises the last thrown exception.

def foo():
    try:
        raise Exception()
    except Exception as e:
        pass

    print 1
    raise
    print 2

if __name__ == '__main__':
    foo()

Any official documentation for this type of usage pattern?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
gli
  • 97
  • 1
  • 6
  • 3
    See http://stackoverflow.com/questions/13957829/how-to-use-raise-keyword-in-python – sebastian_oe Sep 02 '14 at 20:53
  • 1
    *"it must be that the last raise statement simply raises the last thrown exception"* - yes, exactly. *"Any official documentation for this type of usage pattern?"* - see [here](https://docs.python.org/2.7/reference/simple_stmts.html#the-raise-statement): *"If no expressions are present, `raise` re-raises the last exception that was active in the current scope. If no exception is active in the current scope, a `TypeError` exception is raised..."* – jonrsharpe Sep 02 '14 at 21:24
  • hi jonrsharpe, so try and except do not create new scopes? If so, then you have answered my question! – gli Sep 02 '14 at 21:26
  • @gli no, that's still the Local scope - see e.g. http://stackoverflow.com/a/7382643/3001761 – jonrsharpe Sep 02 '14 at 21:29
  • @gli Did you see this in actual code? Or is this just something you thought of? If the former, then I'd be curious to see what the function that calls it looks like. – Rob Watts Sep 02 '14 at 21:31
  • Be *careful* using `raise` in this way in python 2 if you're using nested exception handlers - there's a nasty bit of behavior related to scoping rules that can be very surprising. See http://stackoverflow.com/questions/23707530/previous-error-being-masked-by-current-exception-context – roippi Sep 02 '14 at 21:56

5 Answers5

7

As Russell said,

A bare raise statement re-raises the last caught exception.

It doesn't matter whether this is happening in a try-except block or not. If there has been a caught exception, then calling raise will re-raise that exception. Otherwise, Python will complain that the previously caught exception is None and raise a TypeError because None is not something that can actually be raised.

As tdelaney said, it doesn't seem to make sense to do this except in an error-handling function. Personally I'd say that it doesn't even belong in an error-handling function, as the raise should still be in the except clause. Someone could use this in an attempt to execute code whether or not an error occurs, but a finally clause is the proper way to do that. Another possibility would be using this as a way to determine if an error occurred while executing the function, but there are much better ways to do that (such as returning an extra value that indicates if/where an error occurred).

Community
  • 1
  • 1
Rob Watts
  • 6,866
  • 3
  • 39
  • 58
4

A bare raise statement re-raises the last caught exception. https://docs.python.org/2/tutorial/errors.html#raising-exceptions

Russell Borogove
  • 18,516
  • 4
  • 43
  • 50
3

A Bare raise reraises the current exception. This usually makes no sense at the end of a function, unless the function is called in an exception:

By itself, the raise is invalid and python throws its own exception

>>> def x():
...     raise
>>> x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in x
TypeError: exceptions must be old-style classes or derived from BaseException, not NoneType

But if called within an exception block, it acts sanely

>>> try:
...     int('a')
... except:
...     x()
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError: invalid literal for int() with base 10: 'a'
>>> 

EDIT

This might be a perfectly reasonable thing to do if the function is attempting some sort of recovery. The function could fix what's broken, log a message, trigger the fire extinguishers, etc... and raise if it still thinks the system is in error.

tdelaney
  • 73,364
  • 6
  • 83
  • 116
2

From this documentation we can read:

If no expressions are present, raise re-raises the last exception that was active in the current scope. If no exception is active in the current scope, a TypeError exception is raised indicating that this is an error (if running under IDLE, a Queue.Empty exception is raised instead).

This means that, in the case of your code, if no exception occurs within the try ... except block, then you are forcing the program to raise a TypeError exception to happen.

1

I had a problem like this where I needed to raise a previously caught exception outside the try/except block if my function didn't return a value. I did a bit of looking around in the sys and traceback modules, but couldn't find a good method to do this, so I just ended up storing the exception outside the block.

def foo():
    caught = None

    try:
        raise Exception
    except Exception as e:
        caught = e
        pass

    raise caught


f = foo()

Output

Traceback (most recent call last):
  line 13, in <module>
  line 10, in foo
  line 5, in foo
Exception

Clearly this isn't useful in the above example, but it's pretty useful if you need to try something quite a few times in a loop and re-raise. My specific need was for an HTTP request retry mechanism.

import time

def foo(key):
    caught = None
    
    for i in [1, 2, 3, 4, 5]:
        try:
            return d[key]
        except KeyError as e:
            caught = e
            print(i)
            time.sleep(i)
            continue

    raise caught

d = {"bar": "baz"}

f = foo(key="baz")

Output

1
2
3
4
5
Traceback (most recent call last):
  line 19, in <module>
  line 15, in foo
  line 8, in foo
KeyError: 'baz'
Sam Morgan
  • 2,445
  • 1
  • 16
  • 25
  • This is the answer I thought would work with my use case, and I swear it's worked for me in the past. With Python 3.9.15 now though, if I assign the error `e` to another variable inside of my Except statement, Python completely skipped entering the Except block as if I didn't have a try/except at all. Bizarre! – Joe Flack Jul 26 '23 at 22:14
  • Just tried this again, works perfectly in Python 3.10.10. You've got a bug in your code. – Sam Morgan Jul 28 '23 at 15:58
  • It's super strange. Always worked for me before. Get this: all I had to do was comment out the line where I assigned the error variable within the except block, and my except worked just fine. Put the line back, completely skips except block and errors out as if there was no try/except at all. – Joe Flack Jul 29 '23 at 20:39