PEP: 352 Title: Required Superclass for Exceptions Version: $Revision$
Last-Modified: $Date$ Author: Brett Cannon, Guido van Rossum Status:
Final Type: Standards Track Content-Type: text/x-rst Created:
27-Oct-2005 Python-Version: 2.5 Post-History:

Abstract

In Python 2.4 and before, any (classic) class can be raised as an
exception. The plan for 2.5 was to allow new-style classes, but this
makes the problem worse -- it would mean any class (or instance) can be
raised! This is a problem as it prevents any guarantees from being 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. It also leads to a known hierarchy for all
exceptions to adhere to.

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 *:.[1]

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 often need to be excepted from
the application's exception handling: the default behavior of shutting
the interpreter down without a traceback is usually more desirable than
whatever the application might do (with the possible exception of
applications that emulate Python's interactive command loop with >>>
prompt). 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.

This PEP is based on previous work done for PEP 348.

Requiring a Common Superclass

This PEP proposes introducing a new exception named BaseException that
is a new-style class and has a single attribute, args. Below is the code
as the exception will work in Python 3.0 (how it will work in Python 2.x
is covered in the Transition Plan section):

    class BaseException(object):

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

        Provides an 'args' attribute that contains all arguments passed
        to the constructor.  Suggested practice, though, is that only a
        single string argument be passed to the constructor.

        """

        def __init__(self, *args):
            self.args = args

        def __str__(self):
            if len(self.args) == 1:
                return str(self.args[0])
            else:
                return str(self.args)

        def __repr__(self):
            return "%s(*%s)" % (self.__class__.__name__, repr(self.args))

No restriction is placed upon what may be passed in for args for
backwards-compatibility reasons. In practice, though, only a single
string argument should be used. This keeps the string representation of
the exception to be a useful message about the exception that is
human-readable; this is why the __str__ method special-cases on length-1
args value. Including programmatic information (e.g., an error code
number) should be stored as a separate attribute in a subclass.

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
interpreter 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 they 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 explicitly requested exit from occurring.

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 initial deprecation.

Here is BaseException as implemented in the 2.x series:

    class BaseException(object):

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

        The __getitem__ method is provided for backwards-compatibility
        and will be deprecated at some point.  The 'message' attribute
        is also deprecated.

        """

        def __init__(self, *args):
            self.args = args

        def __str__(self):
            return str(self.args[0]
                       if len(self.args) <= 1
                       else self.args)

        def __repr__(self):
            func_args = repr(self.args) if self.args else "()"
            return self.__class__.__name__ + func_args

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

            Provided for backwards-compatibility and will be
            deprecated.

            """
            return self.args[index]

        def _get_message(self):
            """Method for 'message' property."""
            warnings.warn("the 'message' attribute has been deprecated "
                            "since Python 2.6")
            return self.args[0] if len(args) == 1 else ''

        message = property(_get_message,
                            doc="access the 'message' attribute; "
                                "deprecated and provided only for "
                                "backwards-compatibility")

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.

-   Python 2.5 [done]
    -   all standard exceptions become new-style classes [done]
    -   introduce BaseException [done]
    -   Exception, KeyboardInterrupt, and SystemExit inherit from
        BaseException [done]
    -   deprecate raising string exceptions [done]
-   Python 2.6 [done]
    -   deprecate catching string exceptions [done]
    -   deprecate message attribute (see Retracted Ideas) [done]
-   Python 2.7 [done]
    -   deprecate raising exceptions that do not inherit from
        BaseException
-   Python 3.0 [done]
    -   drop everything that was deprecated above:
        -   string exceptions (both raising and catching) [done]
        -   all exceptions must inherit from BaseException [done]
        -   drop __getitem__, message [done]

Retracted Ideas

A previous version of this PEP that was implemented in Python 2.5
included a 'message' attribute on BaseException. Its purpose was to
begin a transition to BaseException accepting only a single argument.
This was to tighten the interface and to force people to use attributes
in subclasses to carry arbitrary information with an exception instead
of cramming it all into args.

Unfortunately, while implementing the removal of the args attribute in
Python 3.0 at the PyCon 2007 sprint [3], it was discovered that the
transition was very painful, especially for C extension modules. It was
decided that it would be better to deprecate the message attribute in
Python 2.6 (and remove it in Python 2.7 and Python 3.0) and consider a
more long-term transition strategy in Python 3.0 to remove
multiple-argument support in BaseException in preference of accepting
only a single argument. Thus the introduction of message and the
original deprecation of args has been retracted.

References

Copyright

This document has been placed in the public domain.



  Local Variables: mode: indented-text indent-tabs-mode: nil
  sentence-end-double-space: t fill-column: 70 End:

[1] 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

[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

[3] python-3000 email ("How far to go with cleaning up exceptions")
https://mail.python.org/pipermail/python-3000/2007-March/005911.html