PEP:352
Title:Required Superclass for Exceptions
Version:41516
Last-Modified:2005-11-22 11:41:50 -0800 (Tue, 22 Nov 2005)
Author:Brett Cannon <brett at python.org>, Guido van Rossum <guido at python.org>
Status:Draft
Type:Standards Track
Content-Type:text/x-rst
Created:27-Oct-2005
Post-History:

Contents

Abstract

In Python 2.4 and before, any (classic) class can be raised as an exception. The plan is to allow new-style classes starting in Python 2.5, but this makes the problem worse -- it would mean any class (or instance) can be raised! This is a problem since it prevents any guarantees to be made about the interface of exceptions. This PEP proposes introducing a new superclass that all raised objects must inherit from. Imposing the restriction will allow a standard interface for exceptions to exist that can be relied upon.

One might counter that requiring a specific base class for a particular interface is unPythonic. However, in the specific case of exceptions there's a good reason (which has generally been agreed to on python-dev): requiring hierarchy helps code that wants to catch exceptions by making it possible to catch all exceptions explicitly by writing except BaseException: instead of except *:. [2]

Introducing a new superclass for exceptions also gives us the chance to rearrange the exception hierarchy slightly for the better. As it currently stands, all exceptions in the built-in namespace inherit from Exception. This is a problem since this includes two exceptions (KeyboardInterrupt and SystemExit) that are usually meant to signal that the interpreter should be shut down. Changing it so that these two exceptions inherit from the common superclass instead of Exception will make it easy for people to write except clauses that are not overreaching and not catch exceptions that should propagate up and terminate the interpreter.

This PEP is based on previous work done for PEP 348 [1].

Requiring a Common Superclass

This PEP proposes introducing a new exception named BaseException that is a new-style class and has a single attribute, message (that will cause the deprecation of the existing args attribute):

class BaseException(object):

    """Superclass representing the base of the exception hierarchy.

    Provides a 'message' attribute that contains any argument
    passed in during instantiation.

    The 'args' attribute and __getitem__ method are provided for
    backwards-compatibility and will be deprecated at some point.

    """

    def __init__(self, *args):
        """Set 'message' and 'args' attribute"""
        self.args = args
        self.message = args[0] if args else ''

    def __str__(self):
        """Return the str of 'message'"""
        return str(self.message
                   if len(self.args) <= 1
                   else self.args)

    def __unicode__(self):
        """Return the unicode of 'message'"""
        return unicode(self.message
                       if len(self.args) <= 1
                       else self.args)

    def __repr__(self):
        if (len(self.args) <= 1):
            return "%s(%r)" % (self.__class__.__name__, self.message)
        return "%s%r" % (self.__class__.__name__, self.args)

    def __getitem__(self, index):
        """Index into arguments passed in during instantiation.

        Provided for backwards-compatibility and will be
        deprecated.

        """
        return self.args[index]

The message attribute will contain either the first argument passed in at instantiation of the object or the empty string if no arguments were passed in. The attribute is meant to act as a common location to store any extra information that is to be passed along with the exception that goes beyond the location of the exception within the exception hierarchy and the exception's type.

No restriction is placed upon what may be passed in for messsage. This provides backwards-compatibility with how the arguments passed into Exception have no restrictions.

The args attribute is deprecated. While allowing multiple arguments to be passed can be helpful, it is in no way essential. It also does not make it clear which argument is going to be represented by the __str__ method. Restricting initialization to accepting a single argument keeps the API simple and clear. This also means providing a __getitem__ method is unneeded for exceptions and thus will be deprecated as well.

The raise statement will be changed to require that any object passed to it must inherit from BaseException. This will make sure that all exceptions fall within a single hierarchy that is anchored at BaseException [2]. This also guarantees a basic interface that is inherited from BaseException. The change to raise will be enforced starting in Python 3.0 (see the Transition Plan below).

With BaseException being the root of the exception hierarchy, Exception will now inherit from it.

Exception Hierarchy Changes

With the exception hierarchy now even more important since it has a basic root, a change to the existing hierarchy is called for. As it stands now, if one wants to catch all exceptions that signal an error and do not mean the interpreter should be allowed to exit, you must specify all but two exceptions specifically in an except clause or catch the two exceptions separately and then re-raise them and have all other exceptions fall through to a bare except clause:

except (KeyboardInterrupt, SystemExit):
    raise
except:
    ...

That is needlessly explicit. This PEP proposes moving KeyboardInterrupt and SystemExit to inherit directly from BaseException.

- BaseException
  |- KeyboardInterrupt
  |- SystemExit
  |- Exception
     |- (all other current built-in exceptions)

Doing this makes catching Exception more reasonable. It would catch only exceptions that signify errors. Exceptions that signal that the intepreter should exit will not be caught and thus be allowed to propagate up and allow the interpreter to terminate.

KeyboardInterrupt has been moved since users typically expect an application to exit when the press the interrupt key (usually Ctrl-C). If people have overly broad except clauses the expected behaviour does not occur.

SystemExit has been moved for similar reasons. Since the exception is raised when sys.exit() is called the interpreter should normally be allowed to terminate. Unfortunately overly broad except clauses can prevent the exit to occur which had been explicitly requested.

To make sure that people catch Exception most of the time, various parts of the documentation and tutorials will need to be updated to strongly suggest that Exception be what programmers want to use. Bare except clauses or catching BaseException directly should be discouraged based on the fact that KeyboardInterrupt and SystemExit almost always should be allowed to propagate up.

Transition Plan

Since semantic changes to Python are being proposed, a transition plan is needed. The goal is to end up with the new semantics being used in Python 3.0 while providing a smooth transition for 2.x code. All deprecations mentioned in the plan will lead to the removal of the semantics starting in the version following the introduction of the deprecation and the raising of a DeprecationWarning for the version specifically listed.

Deprecation of features in Python 2.9 is optional. This is because it is not known at this time if Python 2.9 (which is slated to be the last version in the 2.x series) will actively deprecate features that will not be in 3.0 . It is conceivable that no deprecation warnings will be used in 2.9 since there could be such a difference between 2.9 and 3.0 that it would make 2.9 too "noisy" in terms of warnings. Thus the proposed deprecation warnings for Python 2.9 will be revisited when development of that version begins to determine if they are still desired.

References

[1]PEP 348 (Exception Reorganization for Python 3.0) http://www.python.org/peps/pep-0348.html
[2](1, 2) python-dev Summary for 2004-08-01 through 2004-08-15 http://www.python.org/dev/summary/2004-08-01_2004-08-15.html#an-exception-is-an-exception-unless-it-doesn-t-inherit-from-exception