[Python-Dev] Pre-PEP: Exception Reorganization for Python 3.0

Brett Cannon bcannon at gmail.com
Sat Jul 30 02:34:07 CEST 2005


Well, it has been discussed at multiple times in the past and I have
promised to write this PEP several times, so I finally found enough
time to write a PEP on reorganizing exceptions for Python 3.0 .

Key points in this PEP is the reworking the hierarchy, requiring
anything raised to inherit from a certain superclass, and to change
bare 'except' clauses to catch a specific superclass.  The first and
last points I expect some contention, but the middle point I expect
people are okay with (Guido liked the idea when Paul Prescod brought
it up and the only person who didn't like it, Holger, ended up being
okay with it when the superclass had a reasonable name).

One thing people might not notice is that I have some minor ideas
listed in the tree in parentheses.  If people have an opinion on the
ideas please speak up.

Otherwise the other major points of contention are covered in the Open
Issues section or will be brought up in the usual trashing of PEPs
that cover contraversial changes.

And please realize this is for Python 3.0!  None of this is being
proposed for any version before then (they could be, but that is
another PEP entirely).  Hopefully this PEP along with Ping's PEP 344
will cover the major ideas for exceptions for Python 3.0 .

-Brett

--------------------------------------------------------------

PEP: XXX
Title: Exception Reorganization for Python 3.0
Version: $Revision: 1.5 $
Last-Modified: $Date: 2005/06/07 13:17:37 $
Author: Brett Cannon <brett at python.org>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 28-Jul-2005
Post-History: XX-XXX-XXX


Abstract
========

Python, as of version 2.4, has 38 exceptions (including warnings) in
the built-in namespace in a rather shallow hierarchy.
This list of classes has grown over the years without a chance to
learn from mistakes and cleaning up the hierarchy.
This PEP proposes doing a reorganization for Python 3000 when
backwards-compatibility is not an issue.
It also proposes changing bare ``except`` clauses to catch only
exceptions inheriting from a specific superclass.
Lastly, this PEP proposes, in Python 3000, that all objects to be
passed to a ``raise`` statement must inherit from a specific
superclass.


Rationale
=========

Exceptions are a critical part of Python.
While exceptions are traditionally used to signal errors in a program,
they have also grown to be used for flow control for things such as
iterators.
There importance is great.

But the organization of the exception hierarchy has not been maintained.
Mostly for backwards-compatibility reasons, the hierarchy has stayed
very flat and old exceptions who usefulness have not been proven have
been left in.
Making exceptions more hierarchical would help facilitate exception
handling by making catching exceptions much more logical with use of
superclasses.
This should also help lead to less errors from being too broad in what
exceptions are caught in an ``except`` clause.

A required superclass for all exceptions is also being proposed
[Summary2004-08-01]_.
By requiring any object that is used in a ``raise`` statement to
inherit from a specific superclass, certain attributes (such as those
laid out in PEP 344 [PEP344]_) can be guaranteed to exist.
This also will lead to the planned removal of string exceptions.

Lastly, bare ``except`` clauses can be made less error-prone
[Summary2004-09-01]_.
Often people use a bare ``except`` when what they really wanted were
non-critical exceptions to be caught while more system-specific ones,
such as MemoryError, to pass through and to halt the interpreter.
With a hierarchy reorganization, bare ``except`` clauses can be
changed to only catch exceptions that subclass a non-critical
exception superclass, allowing more critical exceptions to propagate
to the top of the execution stack as was most likely intended.


Philosophy of Reorganization
============================

There are several goals in this reorganization that defined the
philosophy used to guide the work.
One goal was to prune out unneeded exceptions.
Extraneous exceptions should not be left in since it just serves to
clutter the built-in namespace.
Unneeded exceptions also dilute the importance of other exceptions by
splitting uses between several exceptions when all uses should have
been under a single exception.

Another goal was to introduce any exceptions that were deemed needed
to fill any holes in the hierarchy.
Most new exceptions were done to flesh out the inheritance hierarchy
to make it easier to catch a category of exceptions with a simpler
``except`` clause.

Changing inheritance to make it more reasonable was a goal.
As stated above, having proper inheritance allows for more accurate
``except`` statements when catching exceptions based on the
inheritance tree.

Lastly, any renaming to make an exception's use more obvious from its
name was done.
Having to look up what an exception is meant to be used for because
the name does not proper reflect its usage is annoying and slows down
debugging.
Having a proper name also makes debugging easier on new programmers.


New Hierarchy
=============

Raisable (new; rename BaseException?)
+-- CriticalException (new)
    +-- KeyboardInterrupt
    +-- MemoryError
    +-- SystemExit
    +-- SystemError (subclass SystemExit?)
    +-- AssertionError
    +-- SyntaxError
        +-- IndentationError
            +-- TabError
+-- Exception (replaces StandardError)
    +-- ArithmeticError
        +-- FloatingPointError
        +-- DivideByZeroError
        +-- OverflowError
    +-- ControlFlowException (new)
        +-- StopIteration
        +-- GeneratorExit (introduced by PEP 342 [PEP342]_; subclass
StopIteration?)
    +-- UnicodeError
        +-- UnicodeDecodeError
        +-- UnicodeEncodeError
        +-- UnicodeTranslateError (subclass UnicodeEncodeError and
UnicodeDecodeError?)
    +-- LookupError (better name?)
        +-- IndexError
        +-- KeyError
    +-- TypeError
        +-- AttributeError (subclassing new)
    +-- OSError (does not inherit from EnvironmentError)
        +-- WindowsError
        +-- MacError (new)
        +-- UnixError (new)
    +-- IOError (does not inherit from EnvironmentError)
        +-- EOFError (subclassing new)
    +-- ImportError
    +-- NotImplementedError
    +-- NamespaceException (new)
        +-- UnboundGlobalError (new name for NameError)
        +-- UnboundLocalError (no longer subclasses UnboundGlobalError
which replaces NameError)
    +-- WeakReferenceError (rename for ReferenceError)
    +-- ValueError
+-- Warning
    +-- UserWarning
    +-- PendingDeprecationWarning
        +-- DeprecationWarning (subclassing new)
    +-- SyntaxWarning
    +-- SemanticsWarning (new name for RuntimeWarning)
    +-- FutureWarning


Differences Compared to Python 2.4
==================================

Changes to exceptions from Python 2.4 can take shape in three forms:
removal, renaming, or change in their superclass.
There are also new exceptions introduced in the proposed hierarchy.


New Exceptions
--------------

Raisable
''''''''

The base exception **all** exceptions inherit from.
The name "Raisable" has been chosen to reflect that it is not meant to
be treated as an exception directly, but as the common object that all
things to base passed to ``raise`` must inherit from.


CriticalException
'''''''''''''''''

The superclass for exceptions for which a severe error has occurred
that one would not want to catch accidentally.
The name is meant to reflect the point that these exceptions are
usually raised only when the interpreter should most likely be
terminated.


MacError
''''''''

Introduced for symmertry with WindowsError.


UnixError
'''''''''

Introduced for symmetry with WindowsError.


NamespaceException
''''''''''''''''''

To provide a common superclass for exceptions dealing with namespace
issues, this exception is introduced.
Both UnboundLocalError and UnboundGlobalError (the new name for
NameError) inherit from this class.


Removed Exceptions
------------------

EnvironmentError
''''''''''''''''

Originally meant as an exception for when an event outside of the
interpreter needed to raise an exception, its use has been deemed
unneeded.
While both IOError and OSError inherited this class, the distinction
between OS and I/O is significant enough to not warrant having a
common subclass that one might base an ``except`` clause on.


StandardError
'''''''''''''

Originally meant to act as a superclass for exceptions not considered
critical, its subclassing was rampant and partially negated its
usefulness.
The need for StandardError is negated thanks to the
CriticalException/Exception dichotomy where Exception takes its place.


RuntimeError
''''''''''''
Meant to be used when an existing exception does not fit, its
usefulness is consider meager in Python 2.4 [exceptionsmodule]_.
Also, with the CriticalException/Exception dichotomy, Exception or
CriticalException can be raised directly for the same use.


Renamed Exceptions
------------------

ReferenceError
''''''''''''''

Renamed WeakReferenceError.

ReferenceError was added to the built-in exception hierarchy in Python
2.2 [exceptionsmodule]_.
Taken directly from the ``weakref`` module, its name comes directly
from its original name when it resided in the module.
Unfortunately its name does not suggest its connection to weak
references and thus deserves a renaming.


NameError
'''''''''

Renamed UnboundGlobalError.

While NameError suggests its use, the name does not properly restrict its scope.
With UnboundLocalError already in existence, it seems reasonable to
change NameError to UnboundGlobalError to brings its name more in
line.


RuntimeWarning
''''''''''''''

Renamed SemanticsWarning.

RuntimeWarning is to represent semantic changes coming in the future.
But while saying that affects "runtime" is true, flat-out stating it
is a semantic change is much clearer, eliminating any possible
association of "runtime" with the virtual machine specifically.


Changed Inheritance
-------------------

StopIteration and GeneratorExit
'''''''''''''''''''''''''''''''

Inherit from ControlFlowException.

Collecting all control flow-related exceptions under a common
superclass continues with the theme of maintaining a hierarchy.


AttributeError
''''''''''''''

Inherits TypeError.

Since most attribute access errors can be attributed to an object not
being the type that one expects, it makes sense for AttributeError to
be considered a type error.


IOError
'''''''

No longer subclasses EnvironmentError.

While IOError does fall under the umbrella of EnvironmentError, the
need for EnvironmentError has been deemed wanting, and thus has been
removed.
Thus IOError now subclasses Exception directly.


EOFError
''''''''

Subclasses IOError.

Since an EOF comes from I/O it only makes sense that it be considered
an I/O error.


UnboundGlobalError and UnboundLocalError
''''''''''''''''''''''''''''''''''''''''

Both subclass NamespaceException.

Originally UnboundLocalError subclassed NameError (original name of
UnboundGlobalError), but with both exceptions specifying a certain
namespace, that subclassing has been deemed inappropriate.
Instead, a common class between the two of them has been introduced.


DeprecationWarning
'''''''''''''''''''

Subclasses PendingDeprecationWarning.

Since a DeprecationWarning is a PendingDeprecationWarning that is
happening sooner, if you care about PendingDeprecationWarnings you are
defintely going to care about DeprecationWarnings.


Required Base Class for ``raise``
=================================

By requiring all objects passed to a ``raise`` statement, one is
guaranteed that all exceptions will have certain attributes.
If PEP 342 [PEP344]_ is accepted, the attributes outlined there will
be guaranteed to be on all exceptions raised.
This should help facilitate debugging by making the querying of
information from exceptions much easier.

Technically speaking, this can be easily implemented by modifying
``RAISE_VARARGS`` to do an inheritance check and raise TypeError if it
does not inherit from Raisable.


Bare ``except`` Clauses
=======================

Bare ``except`` clauses typically are not meant to catch all exceptions.
Often times catching KeyboardInterrupt is not intended.
Unfortunately the exception hierarchy in Python 2.4 does not support a
simple way to control what exceptions are caught except to be very
explicit with a list of exceptions.
The reorganization changes this by adding more of a hierarchy.

If bare ``except`` statements only catch classes that inherit from
Exception, its use will be more in line with what people expect.
An implementation can be handled at the bytecode level by modifying
the compiler to emit bytecode to do a check for Exception when a bare
``except`` is used.


Open Issues
===========

Change the Name of Raisable?
----------------------------

It has been argued that Raisable is a bad name to use since it is not
an actual word [python-dev1]_.
At issue is choosing a name that clearly explains the class' usage.
"BaseException" might be acceptable although the name does not quite
make it as explicit as Raisable that the class is not meant to be
raised directly.


Should Bare ``except`` Clauses be Removed?
------------------------------------------

It has been argued that bare ``except`` clauses should be removed
entirely [python-dev2]_.
The train of thought is that it will force people to specify what they
want to catch and not be too broad.
One issue with this, though, is whether people will make the proper
choices of what to catch or go too broadly.
Would a new Python programmer make the right decision and not catch
CriticalExceptions or would they go overboard and catch Raisable?
It would seem the issue boils down to whether we think people will
make proper decisions or make a reasonable solution available for the
simplest case assuming they might make a bad one.


Change the name of Exception?
-----------------------------

Some have suggested names other than "Exception" for the superclass to
inherit from for bare ``except`` clauses to match against.
The issue with going with a name that is not simplistic is that it
raises the chance of people subclassing the wrong exception for what
they want.
By using the most generic name for the common case it raises the
chances that people will use this class for subclassing their own
exceptions rather than Raisable or CriticalException.


Acknowledgements
================

XXX


References
==========

.. [PEP342] PEP 342 (Coroutines via Enhanced Generators)
            (http://www.python.org/peps/pep-0342.html)

.. [PEP344] PEP 344 (Exception Chaining and Embedded Tracebacks)
            (http://www.python.org/peps/pep-0344.html)

.. [exceptionsmodules] 'exceptions' module
            (http://docs.python.org/lib/module-exceptions.html)

.. [Summary2004-08-01] python-dev Summary (An exception is an
exception, unless it doesn't inherit from Exception)
            (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)

.. [Summary2004-09-01] python-dev Summary (Cleaning the Exception House)
            (http://www.python.org/dev/summary/2004-09-01_2004-09-15.html#cleaning-the-exception-house)

.. [python-dev1] python-dev email (Exception hierarchy)
            (http://mail.python.org/pipermail/python-dev/2004-August/047908.html)

.. [python-dev2] python-dev email (Dangerous exceptions)
            (http://mail.python.org/pipermail/python-dev/2004-September/048681.html)


Copyright
=========

This document has been placed in the public domain.


More information about the Python-Dev mailing list