Python Exceptions: Part Six

exceptionsAs a special case for debugging purposes, Python includes the assert statement; it can be thought of as a conditional raise statement. A statement of the form:

	assert , <test> <data>

works like the following code:

	if __debug__:
		if not :
			raise AssertionError()

In other words, if the test evaluates to false, Python raises an exception: the data item is used as the exception’s constructor argument, if a data item is provided. Like all exceptions, the AssertionError exception will kill your program if it’s not caught with a try, in which case the data item shows up as part of the error message. Otherwsie, AssertionError exceptions can be caught and handled like any other exception.

As an added feature, assert statements may be removed from a compiled program’s byte code if the -0 Python command-line flag is used, thus optimizing the program (similar to assert statements in C/C++). AssertionError is a built-in exception, and the __debug__ flag is a built-in name that is automatically set to True unless the -0 flag is used. You can use a command line like python -0 code.py to run in optimized mode and disable asserts.

Assertions are typically used to verify program conditions during development. When displayed, their error message text automatically includes source code line information and the value listed in the assert statement.

As an example, consider a function to convert from Fahrenheit to Celsius. We’ll make it bail out if it sees a temperature less than absolute zero:

def FahrenheitToCelsius(ftemp):
	assert (ftemp >= -460), "Less than absolute zero!"
	return ((ftemp-32)*(5.0/9.0))

FahrenheitToCelsius(32)
FahrenheitToCelsius(55)
FahrenheitToCelsius(-500)

When the above code is executed, it produces the following result:

0.0
12.777777777777779
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in 
    FahrenheitToCelsius(-500)
  File "<pyshell#8>", line 2, in FahrenheitToCelsius
    assert (ftemp >= -460), "Less than absolute zero!"
AssertionError: Less than absolute zero!

It is important to keep in mind that assert is mostly intended for trapping user-defined constraints and not for catching actual programming errors. Because Python traps programming errors itself, there is usually no need to code asserts to catch things like out-of-bounds indexes, type mismatches, and zero divides. Such asserts are generally unnecessary. Because Python raises exceptions on errors automatically, you can let it do the job for you.

With/As Clauses

Python 2.6 and above introduced a new exception-related statement: the with, and its optional as clause. This statement is designed to work with context manager objects, which support a new method-based protocol. The with/as statement is designed to be an alternative to common try/finally statements. Like try/finally, with/as is intended for specifying termination-time or cleanup activity that must run regardless of whether an exception occurs in a processing step. Unlike try/finally, the with statement supports a richer object-based protocol for specifying both entry and exit actions around a block of code.

The basic format of the with statement looks like this:

	with expression [as variable]:
		with-block

The expression here is assumed to return an object that supports the context management protocol. This object may also return a value that will be assigned to the name variable if the optional as clause is present.

Note that the variable is not necessarily assigned the result of the expression. The result of the expression is the object that supports the context protocol, and the variable may be assigned something else intended to be used inside the statement. The object returned by the expression may then run startup code before the with-block is started, as well as termination code after the block is done, regardless of whether the block raised an exception or not.

Some built-in Python objects have been augmented to support the context management protocol, and so can be used with the with statement. For example, file objects have a context manager that automatically closes the file after the with block regardless of whether an exception is raised:

	with open(r'file.txt') as myfile:
		for line in myfile:
			print(line)

Here, the call to open returns a simple file object that is assigned to the name myfile. We can use myfile with the usual file tools. In this case, the file iterator reads line by line in the for loop.

But this object also supports the context management protocol used by the with statement. After this with statement has run, the context management machinery guarantees that the file object referenced by myfile is automatically closed, even if the for loop raised an exception while processing the file.

Although file objects are automatically closed on garbage collection, it is not always easy to know when that will occur. The with statement in this role is an alternative that allows us to be sure that the close will occur after execution of a specific block of code. We can accomplish a similar effect with the more general and explicit try/finally statement, but it requires four lines of code instead of one:

	myfile = open(r'file.txt')
	try:
		for line in myfile:
			print(line)
	finally:
		myfile.close()

The lock and condition synchronization objects they define may also be used with the with statement, because they support the context management protocol:

	lock = threading.lock()
	with lock:
	# critical code
	...access shared resources here...

Here, the context management machinery guarantees that the lock is automatically acquired before the block is executed and released on the block is complete, regardless of exception outcomes.

External Links:

Assertions in Python at www.tutorialspoint.com