Python Exceptions: Part Five

exceptions

The Raise Statement

One of the statements not yet covered in this series on exceptions is the raise statement. To trigger excepts explicitly in Python, you can code raise statements. Their general form is simple – a raise statement consists of the word raise, optionally followed by the class to be raised or an instance of it:

	raise  # Raise an instance of a class
	raise  # Make and raise instance of a class
	raise  # Re-raise the most recent exception

The first raise form presented here is the most common one. We provide an instance directly, either created before the raise or within the raise statement itself. If we pass a class instead, then Python will call the class with no constructor arguments to create an instance to be raised. This form is equivalent to adding parenthesis after the class reference. The last form re-raises the most recently raised exception. It is commonly used in exception handlers to propagate exceptions that have been caught.

With built-in exceptions, the following two forms are equivalent:

	raise IndexError
	raise IndexError()

Both examples raise an instance of the exception class named, although the first creates the instance implicitly. We can also create the instance ahead of time, because the raise statement accepts any kind of object reference:

	my_exc = IndexError()
	raise my_exc

	my_excs = [IndexError, TypeError]
	raise my_excs[0]

When an exception is raised, Python sends the raised instance along with the exception. If a try includes an except name as X: clause, the variable X will be assigned the instance provided in the raise:

	try:
		...
	except IndexError as X: 
		...

The as is optional in the try handler. If it is omitted, the instance is simply not assigned to to a name. Including it allows the handler to access both data in the instance and methods in the exception class.

This model works the same for user-defined exceptions coded in classes:

	class MyExc(Exception): pass
	...
	raise MyExc('wooble')
	...
	try:
		...
	except MyExc as X:
		print(X.args)

Regardless of how exceptions are named, they are always identified by instance objects, and at most one is active at any given time. Once caught by an except clause anywhere in the program, an exception dies unless it is re-raised by another raise statement or error.

A raise statement that does not include an exception name or extra data value simply re-raises the current exception. This form is typically used if you need to catch and handle an exception, but you don’t want the exception to die in your code. Running a raise this way re-raises the exception and propagates it to a higher handler.

Finally, Python (3.0 and above) also allows an optional from clause:

	raise exception from otherexception

When the from is used, the second expression specifies another exception class or instance to attach to the raised exception’s __cause__ atrribute. If the raised exception is not caught, Python prints both exceptions as part of the standard error message:

>>> try:
... 	1/0
...	except Exception as E:
...	raise TypeError('Not good') from E

When an exception is raised inside an exception handler, a similar procedure is followed implicitly. The previous exception is attached to the new exception’s __context__ attribute and is again displayed in the standard error message if the exception goes uncaught.

External Links:

Handling Exceptions at Python Wiki Built-in Exceptions at docs.python.org

Python Exceptions: Part Four

exceptionsPython Exceptions: try/except/finally

In all versions of Python prior to Release 2.5, there were two types of try statements. You could either use a finally to ensure that cleanup code was always run, or write except blocks to catch and recover from specific exceptions and optionally specify an else clause to be run if no exceptions occurred. In other words, the finally clause could not by mixed with except and else.

That has changed with Python 2.5 and later. Now, the two statements have merged; we can mix finally except and else clauses in the same statement:

try:
	statements
except Exception1:
	handler1
except Exception2:
	handler2
else:
	else_block

The code in this statement’s main-action block is executed first, as usual. If that code raises an exception, all the except blocks are tested, one after another, looking for a match to the exception raised. If the exception raised is Exception1, the handler1 block is executed; if it’s Exception2, handler2 is run, etc. If no exception is raised, the else-block is executed.

No matter what’s happened previously, the finally-block is executed once the main action block is complete and any raised exceptions have been handled. In fact, the code in the finally-block will be run even if there is an error in an exception handler or the else-block and a new exception is raised.

As always, the finally clause does not end the exception. If an exception is active when the finally-block is executed, it continues to be propagated after the finally-block runs, and control jumps somewhere else in the program. If no exception is active when the finally is run, control resumes after the entire try statement.

The effect here is that the finally-block is always run, regardless of whether [1] an exception occurred in the main action and was handled; [2] an exception occurred in the main action and was not handled; no exceptions occurred in the main action, and/or [4] a new exception was triggered in one of the handlers. Again, the finally serves to specify cleanup actions that must always occur on the way out of the try, regardless of what exceptions have been raised or handled.

When try, except, else and finally are combined like this, the order must be like this:

	try -> except -> else -> finally

where the else and finally are optional, and there may be zero or more except blocks, but there must be at least one except if an else appears. The try statement essentially consists of two parts: excepts with an optional else, and/or the finally.

Because of these rules, the else can appear only if there is at least one except, and it is always possible to mix except and finally, regardless of whether an else appears to though the except can omit an exception name to catch everything and run a raise statement to re-raise the current exception. If you violate any of these rules, Python will raise a syntax error exception before your code runs.

Finally, prior to Python 2.5, it is actually possible to combine finally and except clauses in a try by syntactically nesting a try/except in the try block of a try/finally statement. The following has the same effect as the new merged form:

try:
	try:
		main-action
	except Exception1:
		handler1
	except Exception2:
		handler2
	...
	else
		no-error
	finally:
		cleanup

Again, the finally block is always run on the way out, regardless of what happened in the main action and regardless of any exception handlers run in the nested try. Since an else always requires an except, this nested form even sports the same mixing constraints of the unified statement form outlined in the preceding section. But this nested equivalent is more obscure and requires more code than the new merged form. Mixing finally into the same statement makes your code easier to write and read and is thus the generally preferred technique.

External Links:

Handling Exceptions at Python Wiki Built-in Exceptions at docs.python.org