Chapter 7. Errors and Exceptions

CONTENTS
  •  Syntax and Sequence Errors
  •  The Danger of Exceptions
  •  The try Statement
  •  The raise Statement
  •  Classes and Instances as Exceptions
  •  Getting the Most Out of Your Exceptions
  •  Summary

Terms in This Chapter

  • Call stack

  • Catchall bucket, exception handler

  • catch statement

  • Class-based exception

  • else clause

  • Error

  • except clause

  • Exception

  • Exception class

  • Exception handling

  • finally clause

  • Granularity

  • Most to least specific order

  • raise statement

  • Throwing versus raising

  • try block

  • try statement

  • Suite

  • User-defined versus built-in exception

Bad things happen to good programs: Disks crash, files are corrupted, and batteries fail. Such things are typically more the exception than the rule, and to plan for all of them would make for some very complicated code. That's why modern programming languages like Python have the ability to handle bad things, called exceptions in programming speak.

The old way of dealing with errors was to check the return types of functions, but this was very time consuming. Each function call would require an if statement with perhaps many elif clauses; code would have to be written to extract what had happened. The new way is exception handling, in which the exception has the information about what went wrong. Instead of checking after each of the method calls, you can set up one exception handler that works with many methods.

By the end of this chapter, you should understand try, raise, and finally statements. You should also have a firm grasp on the class-based exceptions that Python defines. A class-based exception allows you to create a hierarchy of exception classes.

Syntax and Sequence Errors

If you followed along with the interactive sessions in previous chapters as recommended, you ran into errors, most of them involving syntax (I know I have). Here's an example:

>>> for x in xrange(0, 100) ... Traceback (innermost last):   (no code object) at line 0   File "<stdin>", line 1         for x in xrange(0, 100)                                                ^ SyntaxError: invalid syntax

Using SyntaxError: invalid syntax as a clue, we discover that we forgot to put the colon at the end of the for header. There's not much you can do about syntax errors except fix them. As you learn Python, you'll see such errors less and less.

Perhaps some readers have run into exceptions like this one:

>>> list = 1 >>> for x in list: ...     print(`x`) ... Traceback (innermost last):   File "<stdin>", line 1, in ? AttributeError: __getitem__

Here we tried to use an integer like a sequence, and, by throwing an exception, Python is telling us we can't do that.

Before we go any further, I should clear up the difference between and error and an exception. An error usually involves syntax, such as a missing comma, whereas an exception usually involves an operation gone wrong, such as dividing by zero. Think of it this way: Exceptions can be correct syntax but wrong operations.

The Danger of Exceptions

An unhandled exception can stop a program dead in its tracks. This may be okay if you're still developing it, but it's bad if the program has been delivered. Consider the following function (from divby()-1.py), which stops as soon as it runs into a 0 value as a denominator in the list.

def figurePercentage(figures):       for tuple in figures:             numerator = tuple[0]             denominator = tuple[1]             percent = numerator/denominator             print ("The percentage is " + `percent`)

Here the function figurePercentage() expects to receive a sequence of sequences that each contain two numeric fields. So we create a list sequence that contains several tuples, each containing two numeric values, and we invoke figurePercentage() with the sequence as follows:

figures = [(10.0,20.0), (100.0, 200.0), (300.0, 400.0), (1.0, 0.0), (11.0,20.0), (110.0, 200.0) ] figurePercentage(figures)

The figurePercentage() function iterates through each tuple in the list, extracting its 0-indexed item as the numerator and its 1-indexed item as the denominator.

for tuple in figures:       numerator = tuple[0]       denominator = tuple[1]       percent = numerator/denominator       percent = percent * 100       print ("The percentage is " + `percent` + "%")

One tuple has a 0.0 denominator, and we know that we can't divide any number by zero. Let's run this and see what happens.

>>> import divby0_1 The percentage is 50.0% The percentage is 50.0% The percentage is 75.0% Traceback (innermost last):   File "<stdin>", line 1, in ?   File "C:\\.\divby0_1.py", line 10, in ?   File "C:\\.\divby0_1.py", line 5, in figurePercentage ZeroDivisionError: float division >>>

The function stops as soon as it hits the zero denominator because a ZeroDivisionError exception has occurred.

One way to solve such a problem is to prevent it from happening. We can do this by checking for a zero denominator before we divide the numerator by the denominator. In a lot of C and older C++ programs that predate exception handling, this is exactly what you have to do. (We'll cover when to handle a problem as an exception and when to check beforehand for possible errors later in the chapter.)

But what if we don't expect to get a zero value in the denominator? What if this value is an exceptional occurrence? If we add tests for every possible thing that can go wrong, our code can become messy and hard to read. Conversely, if we don't handle this error, our program will just stop working when it receives a zero divisor. Mission-critical programs aren't supposed to stop working for minor problems like this. What if this is a function in a payroll program? Would you want to get paid late because someone forgot to check for a divide-by-zero exception?

The try Statement

The try statement specifies a way to handle exceptions. One form is the following:

try:       suite except expression-target:       suite except:       suite

which can have one or many except clauses the exception handlers. If an exception occurs in the try clause's suite of statements, an exception handler that best fits it will be invoked. An except clause that doesn't have an expression target can act as a catchall bucket. If an exception doesn't have a specific exception handler and an except clause without an expression target is present, the exception will go to that clause.

The Call Stack

A call stack represents the order of called functions. For example, if a module calls function A() and function A() calls function B() and function B() calls function C(), the call stack is A->B->C. If an exception occurs in function C() and isn't handled, it propagates to function B(). If function B() doesn't handle it, the exception propagates to function A(). If function A() doesn't handle it, the program stops.

 

Matching a Handler to an Exception

Unlike in Java, in Python there is no common base class such as Throwable, RuntimeException, or Exception for all exceptions. Even so, Python has generally adopted the Java approach to exception handling, as we'll see later.

Similar to Java's way, in Python an exception matches a handler if the handler is the object that identifies the exception or a base class of the exception, or if the object thrown exactly matches the handler's object identity.

Also, in Python the handler matches the exception if it has a tuple containing an item that matches the exception. This means that you can handle several exceptions in one except clause.

Here is figurePercentage() (from divby0-2.py) expanded with exception handling:

def figurePercentage(figures):       for tuple in figures:              try:                    numerator = tuple[0]                    denominator = tuple[1]                    percent = numerator/denominator                    percent = percent * 100                    print ("The percentage is " + `percent` + "%")              except ZeroDivisionError:                    print ("percentage error") figures = [(10.0,20.0), (100.0, 200.0), (300.0, 400.0), (1.0, 0.0), (11.0,20.0), (110.0, 200.0)] figurePercentage(figures)

The output looks like this:

>>> import divby0_2 The percentage is 50.0% The percentage is 50.0% The percentage is 75.0% percentage error The percentage is 55.0% The percentage is 55.0% >>>

The except Clause

Notice that the program in the example above doesn't stop midway in its iteration through the list. Instead, it prints out that there was an error in one of the percentage calculations and continues on its way. The magic to this is the try statement with the except clause.

try:        numerator = tuple[0]        denominator = tuple[1]        percent = numerator/denominator        print ("The percentage is " + `percent`) except ZeroDivisionError:              print ("percentage error")

In English this says, "Try to execute these statements. If the ZeroDivisionError exception occurs, go to the except clause and execute its statement." This handling of the exception allows the program to continue.

What if someone passes a list that doesn't have just sequences? Follow along in interactive mode.

>>> from divby0_2 import figurePercentage >>> nasty_list = [(1.0, 1.0), (2.0, 2.0), 2, (3.0, 3.0)] >>> figurePercentage(nasty_list)

Here's the output:

The percentage is 1.0 The percentage is 1.0 Traceback (innermost last):   File "<stdin>", line 1, in ?   File "C:\\.\divby0_2.py", line 19, in ?   File "C:\\.\divby0_2.py", line 4, in figurePercentage AttributeError: __getitem__

Even though we handled the divide-by-zero problem, we didn't handle the AttributeError exception. So we add an except clause with an empty target expression to catch this and any other possible exceptions.

The Catchall Exception Handler

Let's continue figuring percentages and add the ability to handle a nonsequence being passed to figurePercentage(). The following example is from divby0_3.py.

def figurePercentage(figures):       for tuple in figures:              try:                    numerator = tuple[0]                    denominator = tuple[1]                    percent = numerator/denominator                    percent = percent * 100                    print ("The percentage is " + `percent` + "%")              except ZeroDivisionError:                    print ("divide by 0 percentage error")              except:                    print ("percentage error") print("The nice list") figures = [(10.0,20.0), (100.0, 200.0), (300.0, 400.0), (1.0, 0.0), (11.0,20.0), (110.0, 200.0)] figurePercentage(figures) print(" -") print("The nasty list") nasty_list = [(1.0, 1.0), (2.0, 2.0), 2, (3.0, 3.0)] figurePercentage(nasty_list)

This time when we run our program, the error is caught. Note that we changed the exception handler of the ZeroDivisionError exception to print that there was a divide-by-zero error. The catchall exception handler prints out that there was a generic percentage error as before. Here's the output:

The nice list The percentage is 50.0% The percentage is 50.0% The percentage is 75.0% divide by 0 percentage error The percentage is 55.0% The percentage is 55.0% - The nasty list The percentage is 100.0% The percentage is 100.0% percentage error The percentage is 100.0%

In essence, the catchall exception handler

except:       print ("percentage error")

says, "If any exception is raised, catch it and print out "percentage error"." To highlight that this statement is a catchall exception, let's send figurePercentage() another list, this time with a sequence that contains only one item (this example is from divby0_3.py).

>>> from divby0_3 import figurePercentage ... ... >>> list1 = [[0,0], [1,1], [2,2], [3], [4,4]] >>> figurePercentage(list1) divide by 0 percentage error The percentage is 100% The percentage is 100% percentage error The percentage is 100%

list[3] is a list with only one item in it, so we get an IndexError exception when we try to get the denominator.

Generally it's a bad idea to sprinkle catchall exception handlers throughout your code. However, instead of a hard and fast rule this is more an issue of style and application specifics. For instance, if you expect a zero in the denominator from time to time, you should test for this rather than rely on catching it as an exception. The same is true if you expect an occasional integer instead of a sequence in a list.

Because we added exception handling for zero denominators, we're saying that we don't expect them, but if we add the check

if(denominator == 0):       print ("The percentage cannot be calculated") elif: percent = numerator/denominator       percent = percent * 100       print ("The percentage is " + `percent` + "%")

we're saying that we do expect them.

Using exception handling is a matter of program requirements; in other words, it's application specific. Exception handling is expensive and should be used only in exceptional cases, not in the normal execution of code. Remember, though, you should never put go code (for beginners, "go code" is code essential to running the program) in an exception handler.

Exception Handling and Interfacing with Other Systems

When you interface with other systems, such as components you didn't write, nonstandard libraries, or, worse, end users, the chances of an exception happening that you can't plan for increase. As I said, one uncaught exception can bring down the whole program, so just before deployment put catchall exception handlers in your code where this interfacing will take place. (At a minimum, the catchall should be able to log the error or you should be able to enable logging with problematic code to catch exceptions.)

You don't want a catchall exception handler when you're developing a program. Rather, you want to identify and handle as many exceptional conditions as needed, especially for mission-critical systems. To avoid a harmless exception bringing down such a system, log all exceptions and evaluate them offline or try to recreate them in a controlled setting and develop a handler for them. You may be able to recover from some exceptions gracefully.

The else Clause

Another feature of the first form of the try statement is the else clause. else's suite is executed only if no exceptions occurred.

try:        suite except expression-target:        suite except:        suite else:        suite The percentage is 1.0 The percentage is 1.0 Traceback (innermost last):   File "<stdin>", line 1, in ?   File "C:\\.\divby0_2.py", line 19, in ?   File "C:\\.\divby0_2.py", line 4, in figurePercentage AttributeError: __getitem__

Using our denominator example (divby0_4.py), if we want to determine if any exceptions occurred, we can add an else clause to the end of our try statement as follows:

def figurePercentage(figures):       for tuple in figures:              try:                    numerator = tuple[0]                    denominator = tuple[1]                    percent = numerator/denominator                    percent = percent * 100                    print ("The percentage is " + `percent` + "%")              except ZeroDivisionError:                    print ("divide by 0 percentage error")              except:                     print ("percentage error")              else:                     print ("No exceptions occurred")

If no exceptions occurred, the figurePercentage() function prints out "No exceptions occurred" during each iteration in which that statement remains true.

The raise Statement

A raise statement forces an exception to be raised. One form is

raise exception-expression, description-expression

In our denominator example, it raises a specific exception.

>>> raise ZeroDivisionError, "Sequence has a Zero value in the denominator" Traceback (innermost last):   File "<stdin>", line 1, in ? ZeroDivisionError: Sequence has a Zero value in the denominator

The exception-expression must be of type String or be an instance object. The description-expression is optional. If you use raise with no exception-expression, it reraises the last exception.

You can easily define your own exception using a string variable.

>>> badListForm = 'badListForm' >>> try: ...     raise badListForm, "The list had a malformed tuple" ... except badListForm, msg: ...     print ("Exception occurred :" + badListForm + " " + msg) ... Exception occurred :badListForm The list had a malformed tuple >>> raise badListForm, "Hi Mom" >>> badListForm = 'badListForm' >>> try: ...     raise badListForm, "The list had a malformed tuple" ... except badListForm, msg: ...     print ("Exception occurred :" + badListForm + " " + msg) ... Exception occurred :badListForm The list had a malformed tuple >>> raise badListForm, "Hi Mom" 

Strings: Bad Style for Exceptions

The use of strings to raise exceptions has fallen out of favor and will likely be phased out of the language. The more "Pythonically" correct method is to use a class instance, which we'll cover a little later. I used the string example here because strings are easy to define and thus make it easy to illustrate exceptions.

Here's how we can modify our figurePercentage() example to handle and reraise (throw) an exception we've already defined:

badListForm = 'badListForm' def figurePercentage(figures):        for tuple in figures:               try:                     numerator = tuple[0]                     denominator = tuple[1]                     percent = numerator/denominator                     percent = percent * 100                     print ("The percentage is " + `percent` + "%")               except ZeroDivisionError:                     print ("divide by 0 percentage error")                     raise               except AttributeError:                     raise badListForm, "An item in the list is not a sequence"               except IndexError:                     raise badListForm, "A sequence in the list does" \                                  " not have two items"               except:                     print ("percentage error")               else:                     #print ("No exceptions occurred")                     pass

First, let's examine the ZeroDivisionError exception handler

except ZeroDivisionError:        print ("divide by 0 percentage error")       raise

which, as we see, prints out an error message and then uses the raise statement with no expressions. (Remember, raise with no expressions reraises the active exception.) The following code snippet passes a list containing a tuple with a zero for the denominator:

try:        print("The nice list")        figures = [(10.0,20.0), (100.0, 200.0), (300.0, 400.0), (1.0, 0.0), (11.0,20.0), (110.0, 200.0)]        figurePercentage(figures) except ZeroDivisionError, msg:        print("Exception:" + `ZeroDivisionError` + ": " + msg)

As soon as figurePercentage() hits the zero denominator tuple ((1,0,0,0)), it catches the exception, prints a message, and then reraises the exception, which in the above code will be caught by the exception handler at the top of the call stack. Here's the output:

The percentage is 50.0% The percentage is 50.0% The percentage is 75.0% divide by 0 percentage error Exception:'ZeroDivisionError': float division

In the following example, we define our own exception, badListForm, and raise it when we get an AttributeError or an IndexError. Notice that the raise statement is used to raise badListForm with two different messages.

badListForm = 'badListForm'        ...        ...        except AttributeError:               raise badListForm, "An item in the list is not a sequence"        except IndexError:               raise badListForm, "A sequence in the list does" \                            " not have two items"

Therefore, if we execute the code snippet

try:        print(" -")        print("The list with a non sequence")        nasty_list = [(1.0, 1.0), (2.0, 2.0), 2, (3.0, 3.0)]        figurePercentage(nasty_list) except badListForm, msg:           print ("Exception:" + `badListForm` + " : " + msg) try:        print(" -")        print("The list with a sequence that contains less items than 2")        nasty_list = [(1.0, 1.0), (2.0, 2.0), [3], (4.0, 4.0)]        figurePercentage(nasty_list) except badListForm, msg:        print ("Exception:" + `badListForm` + " : " + msg)

we get the following output:

 - The list with a non sequence The percentage is 100.0% The percentage is 100.0% Exception:'badListForm' : An item in the list is not a sequence - The list with a sequence that contains less items than 2 The percentage is 100.0% The percentage is 100.0% Exception:'badListForm' : A sequence in the list does not have two items 

Equality Is Not Enough

Having the same value string is not enough. The string exception in the except clause must be exactly the same as the object that was raised, which in Pythonese means

str1 == str2 being true is not enough

instead of

str1 is str2 has to be true.

The finally Clause

The second of the two forms of try cleans up an exception. It includes a finally clause, and looks like this:

try:        suite finally:        suite

You can't use a finally clause and an except clause in the same try statement.

Here's a simple try...finally example:

>>> try: ...    raise "hello", "Hello Error" ...    print "Hello" ... finally: ...    print "Finally" ... Finally Traceback (innermost last):   File "<stdin>", line 2, in ? hello: Hello Error

The try...finally clause guarantees that the finally clause will be executed whether or not an exception occurs. You want cleanup code in a finally clause it's like closing a database connection or a file. We'll use finally a lot when we deal with files in Chapter 8.

Classes and Instances as Exceptions

Earlier we defined our own exception using a string object, making it user defined. The new Python way of defining exceptions is with classes and class instances, which means that the raise statement can take the following form:

raise class, class_instance raise class_instance

In the except clause, you list the name of the class. If the exception instance raised is an instance of that class (a direct instance or an instance of a subclass), except will catch it. The exception class you define should be derived from the Exception class, which is defined in the exception module (exception.py). As of now, this isn't a requirement, but it likely will be in the future. If you use an instance of Exception in conjunction with the str() function, it will return all of the arguments passed to its constructor.

I know these concepts are hard to visualize, so let's do a few quick examples to make them easier to grasp.

Type Exception

First we raise an error of type Exception as follows:

>>> try: ...      error = Exception("Not working", "Something Broke", "Better call for help") ...      raise error ... except Exception: ...      print "Got an error" ... Got an error >>>

We catch the raised exception with our except Exception: handler, which is useful but doesn't do anything with the arguments we passed as our exceptions instance.

The next listing (chap7_1) shows how to use the arguments we passed to the Exception constructor in conjunction with the str statement. (We're going to make things gradually more complex, so be sure to follow along.)

try:       error = Exception("Not working", "Something Broke", "call for help")       raise error except Exception, instance:       print "Got an error " + str( instance)       print instance.__class__.__name__

As you can see, the second argument passed to the exception handler is the instance we raised. When you use the str statement with the instance of the Exception class, you get the argument passed to the constructor converted to a string. The output from the above code is

Got an error ('Not working', 'Something Broke', 'call for help') Exception

Just so we leave no doubt that the second argument to the except clause is in fact the instance we threw (I meant to say "raised" more on this later), we print out the name of the instance class, that is, Exception.

User-Defined Class-Based Exceptions

To round out our examples of class-based exceptions, we'll create our own (that is, user-defined). Of course, we did this before using the old way with string objects, but now we'll do it using classes. Here is a listing (chap7_2) that defines three user-defined exceptions.

Listing chap7_2 class CarException(Exception):        pass class BrakeFailed(CarException):        pass class EngineFailed(CarException):        pass if __name__ == "__main__":        main() def main():        try:                      error = BrakeFailed("Not working", "Something Broke",                                "Better call for help", "Get an Airbag")                      raise error        except BrakeFailed, instance:                      print "Got a BrakeFailed exception " + str(instance) \                      + " " + instance.__class__.__name__        try:                      error = BrakeFailed("Brakes Not working", "Something Broke", "Better call for help", "Get an Airbag")                      raise error        except CarException, instance:                      print "Got a Car Exception " + str(instance) + " " \                      + instance.__class__.__name__        except Exception, instance:                      print "Got an exception " + str(instance) + " " \                      + instance.__class__.__name__        try:                      error = BrakeFailed("Not working", "Something Broke", "Better call for help", "Get an Airbag")                      raise error        except Exception, instance:                      print "Got an exception " + str(instance) + " " \                      + instance.__class__.__name__

Here's the output:

Got a BrakeFailed exception ('Not working', 'Something Broke', 'Better call for help', 'Get an Airbag') BrakeFailed Got an exception ('Not working', 'Something Broke', 'Better call for help', 'Get an Airbag') BrakeFailed Got a Car Exception ('Brakes Not working', 'Something Broke', 'Better call for help', 'Get an Airbag') BrakeFailed

Exception Hierarchy

Using the class approach, you can build a hierarchy of exceptions. This allows programmers who use the methods of one of your classes to decide the level of detail or granularity they care about. For example, you can define 30 CarException classes that can be thrown when the Car class starts that is, when someone calls the class's __start__ method. One user of your Car class may care about all 30. Another may care only if any CarException is thrown and therefore write an except clause only to catch any exception derived from it.

The first try block in the main function of the chap7_2 module raises an instance of BrakeFailed. Here we demonstrate an except clause that catches a BrakeFailed class. The second parameter should be the instance we raised.

try:        error = BrakeFailed("Not working", "Something Broke", "Better call for help", "Get an Airbag")        raise error except BrakeFailed, instance:        print "Got a BrakeFailed exception " + str(instance) + " " \        + instance.__class__.__name__

To take our demonstration further, we show that not only can we catch a BrakeFailed instance with an except clause that specifies a BrakeFailed class, but we can also catch the BrakeFailed instance with any base class of the BrakeFailed base class.

try:        error = BrakeFailed("Brakes Not working", "Something Broke", "Better call for help", "Get an Airbag")        raise error except CarException, instance:        print "Got a Car Exception " + str(instance) + " " \        + instance.__class__.__name__

In the above code, we catch the BrakeFailed instance with an except block that specifies a CarException. We can do this because CarException is a base class of BrakeFailed. We can also catch a BrakeFailed class with any base class.

try:        error = BrakeFailed("Not working", "Something Broke", "Better call for help", "Get an Airbag")        raise error except Exception, instance:        print "Got an exception " + str(instance) + " " \        + instance.__class__.__name__

Here we catch the BrakeFailed instance with the exception handler (the except clause) using the Exception class. With the above approach we can create hierarchies of exceptions that can all be caught by except clauses that specify the exceptions' base classes.

Don't Give Up

If you still don't understand these concepts, fire up the interactive interpreter and import the BrakeFailed class from module chap7_2, and try raising and catching the error inside a try block.

Once again, the only way to learn programming is to program. I guarantee that the people having the hardest time understanding this chapter are probably the ones who aren't following along with the examples. Remember, if a picture is worth a thousand words, a working model is worth a thousand pictures.

Most to Least Specific Order

What happens if you have two except clauses that both catch base classes of the instance you're throwing (raising)? Does the most specific class prevail? By most specific, I mean the class that's closest in the class hierarchy to the class of the instance being raised. Look at the listing for chap7_2, which shows the catch() method.

def catch():        print "the most specific to the least specific"        try:              error = BrakeFailed()              raise error        except BrakeFailed, instance:              print "I got it 1"        except CarException, instance:              print "Excuse me, but I've got it 2"        except Exception, instance:              print "No I beg your pardon, I've got it 3"        print "Least specific to most specific"        try:              error = BrakeFailed()              raise error        except Exception, instance:              print "Exception"        except CarException, instance:              print "CarException"        except BrakeFailed, instance:              print "BrakeFailed"

In the following excerpt from chap7_2.catch(), we see that the BrakeFailed exception is being raised and that there are three except clauses that can potentially catch it.

try:        error = BrakeFailed()        raise error except BrakeFailed, instance:        print "I got it 1" except CarException, instance:        print "Excuse me, but I've got it 2" except Exception, instance:        print "No I beg your pardon, I've got it 3"

The first except can catch BrakeFailed simply because it declares BrakeFailed in its clause. The next except also seems like a candidate because it declares a CarException in its clause and CarException is a base class of BrakeFailed. The third except seems to be a candidate as well because Exception is also a base class of BrakeFailed. (Exception is a direct base class of CarException, and CarException is a base class of BrakeFailed; thus, Exception is a base class of BrakeFailed, and an instance of BrakeFailed is also an instance of Exception.)

If we run the above code segment, we get

I got it 1

So in this case the most specific except caught the exception that is, the first one, which has BrakeFailed in its clause. This being the case, what do you think the following code will do? Is it always the case that the most specific except clause will catch the exception?

try:        error = BrakeFailed()        raise error except Exception, instance:        print "Exception" except CarException, instance:        print "CarException" except BrakeFailed, instance:        print "BrakeFailed"

Unlike the last example, here we organized the order of except clauses from least to most specific. When we run this code, we get the following output:

Exception

Java versus Python versus Delphi versus Visual Basic Exception Handling

Java's handling of exceptions is very similar to Python's. The following table shows the equivalent exception statements in the two languages as well as in Delphi and Visual Basic.

Python Java Delphi (Object Pascal) Visual Basic
try try try on error goto label
except catch except label
raise throw raise raise
finally finally finally on error resume next

Exception handling in Java and Python isn't completely the same. One key difference is that Java allows you to declare the type of exception that a method will throw (that is, "raise" in Python). The method code must catch that exception or its base class.

Another key difference is that it's a syntax error in Java to list catch statements (except clauses in Python) as anything but most to least specific. This feature won't allow you to write code that will never be reached. In my opinion, Python should do something like this or, better yet, should always send the raised exception to the most specific handler (except clause).

Python and Delphi share similar keywords, but Python's syntax for handling errors seems a little closer to Java's than to Delphi's. Regardless, exception handling in the three languages is close. Visual Basic does its own type of error handling.

Thus, the first except that has a base class of the class of the instance we're raising catches that raised instance. The last two in the example are never reached. This proves that the most specific except does not always catch the exception, and it means that we have to be careful in how we organize our except clauses to make sure that they're in most to least specific order.

Garden Variety Exceptions

The exceptions described in the subsections that follow are used as base classes.

Exception

Exception is the root class for all exceptions all built-in exceptions are derived from it, and all user-defined exceptions should be derived from it. The following interactive session shows that ZeroDivisionError is derived from Exception.

>>> try: ...   1/0 ... except Exception, instance: ...   print instance.__class__.__name__ ... ZeroDivisionError
StandardError

StandardError, derived from Exception, is the base class for all built-in exceptions. The following interactive session shows that StandardError is a base class of ZeroDivisionError.

>>> try: ...   1/0 ... except StandardError, error: ...   print error.__class__.__name__ ... ZeroDivisionError

The code uses the StandardError class in the except clause, which will catch all exceptions that are either instances of StandardError or instances of its subclasses. (Remember, all instances of subclasses of StandardError are considered instances of StandardError.)

As I said before, all user-defined exceptions should be subclasses of the Exception class, but don't subclass your exception from StandardError, which is reserved for the built-in variety. It's easy to tell the difference between a system (built-in) exception and an application (or library) exception. It's built in if it's an instance of a StandardError subclass.

ArithmeticError

ArithmeticError is the base class for built-in exceptions representing arithmetic errors, such as OverflowError, ZeroDivisionError, and FloatingPointError.

>>> try: ...   1/0 ... except ArithmeticError, aerror: ...   print aerror ...   print aerror.__class__.__name__ integer division or modulo ZeroDivisionError
LookupError

LookupError is a base class for the exceptions associated with items not in a collection object (mapping or sequence) for example, IndexError and KeyError. The following example shows how to catch a LookupError:

>>> dictionary = {"Hello":"Hi", "Goodbye":"Bye", "See ya Later":"Later"} >>> print dictionary["Hello"] Hi >>> try: ...   print dictionary["Ciao"] ... except LookupError, error: ...   print error.__class__.__name__ ... KeyError

We can see that KeyError is a subclass of LookupError, from which it derives.

AttributeError

AttributeError is used in conjunction with classes and instances. It signifies an attribute reference or a failed assignment.

>>> class clazz: ...   a = 1 ...   b = "hi" ...   c = None ... >>> clazz.b 'hi' >>> try: ...   clazz.a = clazz.e ... except AttributeError, error: ...   print "Class does not support attribute e" ... Class does not support attribute e
EOFError

EOFError signifies that a built-in function has hit the end of the file possibly unexpectedly. We'll leave this example to Chapters 8 and 9.

FloatingPointError

FloatingPointError signifies the failure of a floating-point operation.

IOError

IOError signifies failure of an I/O operation. We'll discuss this more in Chapter 9.

IndexError

IndexError signifies that a subscript is out of range. The following example shows how you might get such an exception:

>>> seq = (1,2,3,4,5) >>> seq[1] 2 >>> seq[5] Traceback (innermost last):   File "<stdin>", line 1, in ? IndexError: tuple index out of range
KeyError

KeyError signifies a key not found in a dictionary, which the following example demonstrates:

>>> dictionary = {"Hello":"Hi", "Goodbye":"Bye", "See ya Later":"Later"} >>> print dictionary["Hello"] Hi >>> try: ...   print dictionary["Ciao"] ... except KeyError, error: ...   print "Ciao not in dictionary" ... Ciao not in dictionary >>>

Does this example look familiar? As an exercise, compare it to the one for LookupError.

NameError

NameError signifies that a name isn't found in the current namespace.

>>> name1 = None >>> name2 = None >>> name1 >>> name2 >>> name3 Traceback (innermost last):   File "<stdin>", line 1, in ? NameError: name3

Here two variables, name1 and name2, are defined. After the interactive session accesses their values, it tries to access a variable that it didn't define. Thus, an exception is uncaught and displayed in the interactive interpreter.

To catch the exception, we do this:

>>> try: ...   print name3 ... except NameError, error: ...   print "name3 is not present" ... name3 is not present >>>
OverflowError

OverflowError signifies that a number is too big to fit in a number variable.

TypeError

TypeError signifies that the code is trying to use a built-in object with a function or operation that doesn't support it. In this example, we try to multiply a list by another list, which can't be done. We'll try to get a TypeError.

>>> list = [1,2,3] >>> list2 = [1,2,3] >>> list = list * list2 Traceback (innermost last):   File "<console>", line 1, in ? TypeError: can't multiply sequence with non-int

Do you remember that a dictionary key has to be immutable? If you use a mutable object as a key, what happens? Fire up the interactive interpreter, and try the following example:

>>> dict = {} >>> list = [1,2,3] >>> dict[list]="item" Traceback (innermost last):   File "<console>", line 1, in ? TypeError: unhashable type
ValueError

ValueError signifies the wrong type for the current operation or function. In this example, the string module uses it to indicate that a string can't be evaluated to a number when the atoi (ASCII-to-Integer) function is called (see Chapter 10).

>>> import string >>> string.atoi("123") 123 >>> string.atoi("YOU") Traceback (innermost last):   File "<interactive input>", line 0, in ? ValueError: invalid literal for atoi(): YOU

First the code imports the string module and then invokes the module's atoi function to convert the string "123" to an integer. Next it uses atoi to convert the string "YOU" to an integer, which raises a ValueError exception because that string has no integer equivalent.

ValueError is used in all kinds of modules. It's a nice way of saying that the value is out of the range a particular function was expecting.

ZeroDivisionError

We used ZeroDivisionError a lot in the examples earlier in the chapter. Remember that it occurs when you have a zero in the denominator.

>>> 1/0 Traceback (innermost last):   File "<stdin>", line 1, in ?   ZeroDivisionError: integer division or modulo

Getting the Most Out of Your Exceptions

Exception class instances have the attributes filename, lineno, offset, and text. You can access these details to locate where your code failed. The next example (chap7_3.py) shows how:

class MyException(Exception):        pass def raiseMyException():        raise Exception() def catchMyException():        try:              raiseMyException()        except MyException, err:              print "line number " +err.lineno              print "file name " + err.filename              print "offset " + err.offset              print " text " + err.text

Summary

Exception handling is easier than the traditional approach of always checking the return type from functions. Exceptions can be class objects or string objects, although string object exceptions are no longer in favor. The newer, more correct way is to use class instances. In fact, as of Python version 1.5, all of the standard exceptions are class instances.

The try statement tries a block of code; if that block raises an exception, try either handles that exception or performs some type of cleanup. There are two forms of try. One has try, except, and else clauses. The except clause handles all exception classes derived from the class it mentions. The else clause is the default handler for code that must be executed if no exceptions are raised.

The other form of try has a finally clause. finally guarantees that some form of cleanup code (like file closing) is performed. The raise statement allows programmers to raise an exception in their code.

CONTENTS


Python Programming with the JavaT Class Libraries. A Tutorial for Building Web and Enterprise Applications with Jython
Python Programming with the Javaв„ў Class Libraries: A Tutorial for Building Web and Enterprise Applications with Jython
ISBN: 0201616165
EAN: 2147483647
Year: 2001
Pages: 25

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net