Exceptions


I've talked about exceptions before but now I will talk about them in depth. Essentially, exceptions are events that modify program's flow, either intentionally or due to errors. They are special events that can occur due to an error, e.g. trying to open a file that doesn't exist, or when the program reaches a marker, e.g. the completion of a loop. Exceptions, by definition, don't occur very often; hence, they are the "exception to the rule" and a special class has been created for them. 

Exceptions are everywhere in Python. Virtually every module in the standard Python library uses them, and Python itself will raise them in a lot of different circumstances. Here are just a few examples:
· Accessing a non−existent dictionary key will raise a KeyError exception.
· Searching a list for a non−existent value will raise a ValueError exception.
· Calling a non−existent method will raise an AttributeError exception.
· Referencing a non−existent variable will raise a NameError exception.
· Mixing datatypes without coercion will raise a TypeError exception. 
One use of exceptions is to catch a fault and allow the program to continue working; we have seen this before when we talked about files. This is the most common way to use exceptions. When programming with the Python command line interpreter, you don't need to worry about catching exceptions. Your program is usuall short enough to not be hurt too much if an exception occurs. Plus, having the exception occur at the command line is a quick and easy way to tell if your code logic has a problem. However, if the same error occured in your real program, it will fail and stop working. 

Exceptions can be created manually in the code by raising an exception. It operates exactly as a system-caused exceptions, except that the programmer is doing it on purpose. This can be for a number of reasons. One of the benefits of using exceptions is that, by their nature, they don't put any overhead on the code processing. Because exceptions are supposed to happen very often, they aren't processed until they occur. 
  
Exceptions can be thought of as a special form of the if/elif statements. You can realistically do the same thing with if blocks as you can with exceptions. However, as already mentioned, exceptions aren't processed until they occur; if blocks are processed all the time. Proper use of exceptions can help the performance of your program. The more infrequent the error might occur, the better off you are to use exceptions; using if blocks requires Python to always test extra conditions before continuing. Exceptions also make code management easier. If your programming logic is mixed in with error-handling if statements, it can be difficult to read, modify, and debug your program.

Here is a simple program that highlights most of the important features of exception processing. It simply produces the quotient of 2 numbers. 
  
first_number = raw_input("Enter the first number.")
sec_number = raw_input("Enter the second number.")

try:
    num1 = float(first_number)
    num2 = float(sec_number)
    result = num1/num2
except ValueError:
    print "Two numbers are required."
except ZeroDivisionError:
    print "Zero can't be a denominator."
else:
    print str(num1) + "/" + str(num2) + "=" + str(result)

    #alternative format (a tuple is required for multiple values)
    #print "%.1f/%.1f=%.1f" % (num1, num2, result) 


As you can see, you can have several "exception catchers" in the same try block. You can also use the else statement at the end to denote the logic to perform if all goes well; however, it’s not necessary and I usually don’t think about including it. Notice that the whole try block could also have been written as if/elif statements but that would have required Python to process each statement to see if they matched. By using exceptions, the "default" case is assumed to be true until an exception actually occurs. These speeds up processing. 
One change you could make to this program is to simply put it all within the try block. The raw_input() variables could be placed within the try block, replacing the "num1" and "num2" variables by forcing the user input to a float value, like so: 
  
try: 
    numerator = float(raw_input("Enter the numerator.")) 
    denominator = float(raw_input("Enter the denominator.")) 


This way, you reduce the amount of logic that has to be written, processed, and tested. You can still have the same exceptions, you're just simplifying the program. 
Finally, it's better to include error-checking, such as exceptions, in your code as you program rather than as an afterthought. A special "category" of programming involves writing test cases to ensure that most possible errors are accounted for in the code, especially as the code changes or new versions are created. By planning ahead and putting exceptions and other error-checking code into your program at the outset, you ensure that problems are caught before they can cause problems. By updating your test cases as your program evolves, you ensure that version upgrades maintain compatability and a fix doesn't create an error condition. 

Exception Class Hierarchy 

Below is a diagram showing the hierarchy of exceptions from the Python Library Reference. When an exception occurs, it starts at the lowest level possible (a child) and travels upward (through the parents), waiting to be caught. This means a couple of things to a programmer. 
If you don't know what exception may occur, you can always just catch a higher level exception. For example, if you didn't know that ZeroDivisionError from the previous example was a "stand-alone" exception, you could have used the ArithmeticError for the exception and caught that; as the diagram shows, ZeroDivisionError is a child of ArithmeticError, which in turn is a child of StandardError, and so on up the hierarchy. 
Another thing to consider is that multiple exceptions can be treated the same way. Following on with the above example, if you plan on using the ZeroDivisionError and you want to include the FloatingPointError and you want to have the same action taken for both errors, you could simply use their parent ArithmeticError as the exception to catch.That way, when either a floating point or zero division error occurs, you don't have to have a separate case for each one. 
  
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- Exception
      +-- GeneratorExit
      +-- StopIteration
      +-- StandardError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |    |    +-- UnicodeError
      |    |         +-- UnicodeDecodeError
      |    |         +-- UnicodeEncodeError
      |    |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
   +-- ImportWarning
   +-- UnicodeWarning

User-Defined Exceptions 

I won't spend too much time talking about this, but Python does allow for a programmer to create his own exceptions. You probably won't have to do this very often but it's nice to have the option when necessary. However, before making your own exceptions, make sure there isn't one of the built-in exceptions that will work for you. They have been "tested by fire" over the years and not only work effectively, they have been optimized for performance and bugs. 
Making your own exceptions involves object-oriented programming, which will be covered in the next chapter. To make a custom exception, the programmer determines which base exception to use as the class to inherit from, e.g. making an exception for negative numbers or one for imaginary numbers would probably fall under the ArithmeticError exception class. To make a custom exception, simply inherit the base exception and define what it will do. Here's an example from "Python How to Program": 
class NegativeNumberError( ArithmeticError ): 
    pass

def squareRoot( number ):
    if number < 0:
        raise NegativeNumberError, \
        "Square root of negative number not permitted" 

    return math.sqrt( number ) 


The first line creates the custom exception NegativeNumberError, which inherits from ArithmeticError. Because it inherits all the features of the base exception, you don't have to define anything else, hence the pass statement that signifies that no actions are to be performed. Then, to use the new exception, a function is created (squareRoot()) that calls the NegativeNumberError