Learning Python - Mark Lutz [470]
A subtle point to note here is that you generally must redefine __str__ for this purpose, because the built-in superclasses already have a __str__ method, and __str__ is preferred to __repr__ in most contexts (including printing). If you define a __repr__, printing will happily call the superclass’s __str__ instead! See Chapter 29 for more details on these special methods.
Whatever your method returns is included in error messages for uncaught exceptions and used when exceptions are printed explicitly. The method returns a hardcoded string here to illustrate, but it can also perform arbitrary text processing, possibly using state information attached to the instance object. The next section looks at state information options.
Custom Data and Behavior
Besides supporting flexible hierarchies, exception classes also provide storage for extra state information as instance attributes. As we saw earlier, built-in exception superclasses provide a default constructor that automatically saves constructor arguments in an instance tuple attribute named args. Although the default constructor is adequate for many cases, for more custom needs we can provide a constructor of our own. In addition, classes may define methods for use in handlers that provide precoded exception processing logic.
Providing Exception Details
When an exception is raised, it may cross arbitrary file boundaries—the raise statement that triggers an exception and the try statement that catches it may be in completely different module files. It is not generally feasible to store extra details in global variables because the try statement might not know which file the globals reside in. Passing extra state information along in the exception itself allows the try statement to access it more reliably.
With classes, this is nearly automatic. As we’ve seen, when an exception is raised, Python passes the class instance object along with the exception. Code in try statements can access the raised instance by listing an extra variable after the as keyword in an except handler. This provides a natural hook for supplying data and behavior to the handler.
For example, a program that parses data files might signal a formatting error by raising an exception instance that is filled out with extra details about the error:
>>> class FormatError(Exception):
... def __init__(self, line, file):
... self.line = line
... self.file = file
...
>>> def parser():
... raise FormatError(42, file='spam.txt') # When error found
...
>>> try:
... parser()
... except FormatError as X:
... print('Error at', X.file, X.line)
...
Error at spam.txt 42
In the except clause here, the variable X is assigned a reference to the instance that was generated when the exception was raised.[78] This gives access to the attributes attached to the instance by the custom constructor. Although we could rely on the default state retention of built-in superclasses, it’s less relevant to our application:
>>> class FormatError(Exception): pass # Inherited constructor
...
>>> def parser():
... raise FormatError(42, 'spam.txt') # No keywords allowed!
...
>>> try:
... parser()
... except FormatError as X:
... print('Error at:', X.args[0], X.args[1]) # Not specific to this app
...
Error at: 42 spam.txt
Providing Exception Methods
Besides enabling application-specific state information, custom constructors also better support extra behavior for exception objects. That is, the exception class can also define methods to be called in the handler. The following, for example, adds a method that uses exception state information to log errors to a file:
class FormatError(Exception):
logfile = 'formaterror.txt'
def __init__(self, line, file):
self.line = line
self.file = file
def logerror(self):
log = open(self.logfile, 'a')
print('Error at', self.file, self.line, file=log)
def parser():
raise FormatError(40, 'spam.txt')
try:
parser()
except FormatError as exc:
exc.logerror()
When run, this script writes its error message to a file