Snake_Byte #17: raise Hell()

The raise statement is an often used, but rarely understood feature of Python. Case in point: the official Python tutorial reads, "The sole argument to raise indicates the exception to be raised." However, when we check the official language reference, we find that the raise keyword actually takes up to three arguments. "But wait," you might be thinking, "I use raise all the time and I've never needed to pass three arguments." This is because the Python interpreter helpfully autopopulates the necessary expressions depending on the inputs given to the raise keyword.

Let's take a look at how most programmers use raise:

An instance of the MyException class is initialized with an error message, and then raised, right? Well, what's actually going on looks something more like this:

So what's happening here? The raise keyword accepts three expressions: an Exception subclass, an instance of that subclass (or arguments that can used to create one), and a traceback object. However, if raise is given only one argument, and that argument is an instance of an Exception subclass, then it will define expression 1 as the argument's class, expression 2 as the argument's instance, and expression 3 as None (which creates the default traceback object).

Similarly, if no arguments are provided, then raise will generate the expressions using the last Exception to be handled. This allows us to respond to an error, even if we can't or won't handle it, without losing the traceback:

Notice that when we explicitly re-raise e, the traceback is from the second raise statement onwards, omitting any reference to the function where the error was actually raised. If you take away one thing from this it should be that you should never explicitly re-raise an exception, just use a blank raise instead.

The traceback will similarly be obscured if you want to raise a different Exception during an except clause, and this is where knowledge of raise's three exceptions is useful:

"But we fixed that YEARS ago!" cry all of the developers who are fortunate enough to work exclusively with Python 3. It's true, in Python 3, a raise statement inside of an except clause will automatically include the tracebacks from previously handled errors. This means that 'raise' and 'raise e' are more or less equivalent. Furthermore, the above is not valid Python 3 code, as raise no longer accepts three arguments. Instead, Python 2 writers concerned with forward compatibility can take advantage of the future.utils _raise method to achieve the same effect.

So, there you have it: If you need to re-raise an Exception use Python 3 if you can; use _raise if you can't; and never raise an Exception that you didn't initialize. Now get out there and raise SomeHell().