So far, I’ve been deliberately vague about what an exception actually is. As suggested in the prior chapter, as of Python 2.6 and 3.0 both built-in and user-defined exceptions are identified by class instance objects. This is what is raised and propagated along by exception processing, and the source of the class matched against exceptions named in try statements.
Although this means you must use object-oriented programming to define new exceptions in your programs—and introduces a knowledge dependency that deferred full exception coverage to this part of the book—basing exceptions on classes and OOP offers a number of benefits. Among them, class-based exceptions:
Can be organized into categories. Exceptions coded as classes support future changes by providing categories—adding new exceptions in the future won’t generally require changes in try statements.
Have state information and behavior. Exception classes provide a natural place for us to store context information and tools for use in the try handler—instances have access to both attached state information and callable methods.
Support inheritance. Class-based exceptions can participate in inheritance hierarchies to obtain and customize common behavior—inherited display methods, for example, can provide a common look and feel for error messages.
Because of these advantages, class-based exceptions support program evolution and larger systems well. As we’ll find, all built-in exceptions are identified by classes and are organized into an inheritance tree, for the reasons just listed. You can do the same with user-defined exceptions of your own.
In fact, in Python 3.X the built-in exceptions we’ll study here turn out to be integral to new exceptions you define. Because 3.X requires user-defined exceptions to inherit from built-in exception superclasses that provide useful defaults for printing and state retention, the task of coding user-defined exceptions also involves understanding the roles of these built-ins.
Version skew note: Python 2.6, 3.0, and later require exceptions to be defined by classes. In addition, 3.X requires exception classes to be derived from the BaseException built-in exception superclass, either directly or indirectly. As we’ll see, most programs inherit from this class’s Exception subclass, to support catchall handlers for normal exception types—naming it in a handler will thus catch everything most programs should. Python 2.X allows standalone classic classes to serve as exceptions, too, but it requires new-style classes to be derived from built-in exception classes, the same as 3.X.