Conditionally raising an exception in Python: short-circuit evaluation of “raise”

Probably you have used a shortcut construct like that:

def conditional_result(switch):
    if switch:
        return result_a
    return result_b

Obviously, an else is not required, because the execution flow leaves the function once it comes across a return statement. Have you ever felt doing the same with exceptions? In other words:

def conditional_error(switch):
    if switch:
        raise ErrorA
    raise ErrorB

Does that work the same way? Sure. Sure? After ErrorA is raised — can the execution sequence ever magically re-enter the function conditional_error right below the if block, where it was exceptionally left? Generators can do this (with the yield statement) .

The answer is that the above code is safe and behaves as expected, analogous to the conditional_result function. The explanation from A Programmer’s Introduction to Python:

The raise statement does two things: it creates an exception object, and immediately leaves the expected program execution sequence to search the enclosing try statements for a matching except clause. The effect of a raise statement is to either divert execution in a matching except suite, or to stop the program because no matching except suite was found to handle the exception.

For the conditional_error function above this means: if there is a matching except clause, it is outside of the function (there definitely is no handler within the function, do you see one?). In this case the execution flow steps out of the function without saving the current state (as a generator or a co-routine in general would do) — the function is really, absolutely left and the program proceeds at a different point: the matching except clause. If there is no matching except clause, the program stops.

This is how it looks live:

class ErrorA(Exception):
    pass
 
 
class ErrorB(Exception):
    pass
 
 
def conditional_error(switch):
    if switch:
        raise ErrorA
    raise ErrorB
 
 
try:
    conditional_error(True)
except ErrorA:
    print("error A caught!")
 
 
try:
    conditional_error(False)
except ErrorB:
    print("error B caught!")
 
 
conditional_error(False)

With the following output:

$ python test.py
error A caught!
error B caught!
Traceback (most recent call last):
  File "test.py", line 26, in <module>
    conditional_error(False)
  File "test.py", line 11, in conditional_error
    raise ErrorB
__main__.ErrorB
  • Praxis

    Wouldn’t it be neat if there was a conditional except statement? e.g. :
    try:

    except if :
    do stuff

    It wouldn’t be too major an improvement and may be more trouble than it’s worth, but it would save you writing the conditional statement in the main code and then raising an arbitrary exception…

    • I do not know how a except if : idiom should work. Where would you specifiy the exception type to look out for? except IOError if :? In case you actually mean raise IOError if , then I would say it is largely the same as writing if : raise IOError. As you say, the additional language / parsing rule would not be worth the gain I think.