Learning Python - Mark Lutz [464]
Here are some of the advantages of class-based exceptions:
They can be organized into categories. Exception classes support future changes by providing categories—adding new exceptions in the future won’t generally require changes in try statements.
They have attached state information. Exception classes provide a natural place for us to store context information for use in the try handler—they may have both attached state information and callable methods, accessible through instances.
They 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. In fact, 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 Python 3.0, user-defined exceptions inherit from built-in exception superclasses. As we’ll see here, because these superclasses provide useful defaults for printing and state retention, the task of coding user-defined exceptions also involves understanding the roles of these built-ins.
* * *
Note
Version skew note: Python 2.6 and 3.0 both require exceptions to be defined by classes. In addition, 3.0 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 catch everything most programs should. Python 2.6 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.0.
* * *
Exceptions: Back to the Future
Once upon a time (well, prior to Python 2.6 and 3.0), it was possible to define exceptions in two different ways. This complicated try statements, raise statements, and Python in general. Today, there is only one way to do it. This is a good thing: it removes from the language substantial cruft accumulated for the sake of backward compatibility. Because the old way helps explain why exceptions are as they are today, though, and because it’s not really possible to completely erase the history of something that has been used by a million people over the course of nearly two decades, let’s begin our exploration of the present with a brief look at the past.
String Exceptions Are Right Out!
Prior to Python 2.6 and 3.0, it was possible to define exceptions with both class instances and string objects. String-based exceptions began issuing deprecation warnings in 2.5 and were removed in 2.6 and 3.0, so today you should use class-based exceptions, as shown in this book. If you work with legacy code, though, you might still come across string exceptions. They might also appear in tutorials and web resources written a few years ago (which qualifies as an eternity in Python years!).
String exceptions were straightforward to use—any string would do, and they matched by object identity, not value (that is, using is, not ==):
C:\misc> C:\Python25\python
>>> myexc = "My exception string" # Were we ever this young?
>>> try:
... raise myexc
... except myexc:
... print('caught')
...
caught
This form of exception was removed because it was not as good as classes for larger programs and code maintenance. Although you can’t use string exceptions today, they actually provide a natural vehicle for introducing the class-based exceptions model.
Class-Based Exceptions
Strings were a simple