457 lines
14 KiB
ReStructuredText
457 lines
14 KiB
ReStructuredText
PEP: 317
|
||
Title: Eliminate Implicit Exception Instantiation
|
||
Version: $Revision$
|
||
Last-Modified: $Date$
|
||
Author: Steven Taschuk <staschuk@telusplanet.net>
|
||
Status: Rejected
|
||
Type: Standards Track
|
||
Content-Type: text/x-rst
|
||
Created: 06-May-2003
|
||
Python-Version: 2.4
|
||
Post-History: 09-Jun-2003
|
||
|
||
|
||
Abstract
|
||
========
|
||
|
||
"For clarity in new code, the form ``raise class(argument, ...)``
|
||
is recommended (i.e. make an explicit call to the constructor)."
|
||
|
||
-- Guido van Rossum, in 1997 [1]_
|
||
|
||
This PEP proposes the formal deprecation and eventual elimination of
|
||
forms of the ``raise`` statement which implicitly instantiate an
|
||
exception. For example, statements such as ::
|
||
|
||
raise HullBreachError
|
||
raise KitchenError, 'all out of baked beans'
|
||
|
||
must under this proposal be replaced with their synonyms ::
|
||
|
||
raise HullBreachError()
|
||
raise KitchenError('all out of baked beans')
|
||
|
||
Note that these latter statements are already legal, and that this PEP
|
||
does not change their meaning.
|
||
|
||
Eliminating these forms of ``raise`` makes it impossible to use string
|
||
exceptions; accordingly, this PEP also proposes the formal deprecation
|
||
and eventual elimination of string exceptions.
|
||
|
||
Adoption of this proposal breaks backwards compatibility. Under the
|
||
proposed implementation schedule, Python 2.4 will introduce warnings
|
||
about uses of ``raise`` which will eventually become incorrect, and
|
||
Python 3.0 will eliminate them entirely. (It is assumed that this
|
||
transition period -- 2.4 to 3.0 -- will be at least one year long, to
|
||
comply with the guidelines of :pep:`5`.)
|
||
|
||
|
||
Motivation
|
||
==========
|
||
|
||
String Exceptions
|
||
-----------------
|
||
|
||
It is assumed that removing string exceptions will be uncontroversial,
|
||
since it has been intended since at least Python 1.5, when the
|
||
standard exception types were changed to classes [1]_.
|
||
|
||
For the record: string exceptions should be removed because the
|
||
presence of two kinds of exception complicates the language without
|
||
any compensation. Instance exceptions are superior because, for
|
||
example,
|
||
|
||
* the class-instance relationship more naturally expresses the
|
||
relationship between the exception type and value,
|
||
|
||
* they can be organized naturally using superclass-subclass
|
||
relationships, and
|
||
|
||
* they can encapsulate error-reporting behaviour (for example).
|
||
|
||
|
||
Implicit Instantiation
|
||
----------------------
|
||
|
||
Guido's 1997 essay [1]_ on changing the standard exceptions into
|
||
classes makes clear why ``raise`` can instantiate implicitly:
|
||
|
||
"The raise statement has been extended to allow raising a class
|
||
exception without explicit instantiation. The following forms,
|
||
called the "compatibility forms" of the raise statement [...] The
|
||
motivation for introducing the compatibility forms was to allow
|
||
backward compatibility with old code that raised a standard
|
||
exception."
|
||
|
||
For example, it was desired that pre-1.5 code which used string
|
||
exception syntax such as ::
|
||
|
||
raise TypeError, 'not an int'
|
||
|
||
would work both on versions of Python in which ``TypeError`` was a
|
||
string, and on versions in which it was a class.
|
||
|
||
When no such consideration obtains -- that is, when the desired
|
||
exception type is not a string in any version of the software which
|
||
the code must support -- there is no good reason to instantiate
|
||
implicitly, and it is clearer not to. For example:
|
||
|
||
1. In the code ::
|
||
|
||
try:
|
||
raise MyError, raised
|
||
except MyError, caught:
|
||
pass
|
||
|
||
the syntactic parallel between the ``raise`` and ``except``
|
||
statements strongly suggests that ``raised`` and ``caught`` refer
|
||
to the same object. For string exceptions this actually is the
|
||
case, but for instance exceptions it is not.
|
||
|
||
2. When instantiation is implicit, it is not obvious when it occurs,
|
||
for example, whether it occurs when the exception is raised or when
|
||
it is caught. Since it actually happens at the ``raise``, the code
|
||
should say so.
|
||
|
||
(Note that at the level of the C API, an exception can be "raised"
|
||
and "caught" without being instantiated; this is used as an
|
||
optimization by, for example, ``PyIter_Next``. But in Python, no
|
||
such optimization is or should be available.)
|
||
|
||
3. An implicitly instantiating ``raise`` statement with no arguments,
|
||
such as ::
|
||
|
||
raise MyError
|
||
|
||
simply does not do what it says: it does not raise the named
|
||
object.
|
||
|
||
4. The equivalence of ::
|
||
|
||
raise MyError
|
||
raise MyError()
|
||
|
||
conflates classes and instances, creating a possible source of
|
||
confusion for beginners. (Moreover, it is not clear that the
|
||
interpreter could distinguish between a new-style class and an
|
||
instance of such a class, so implicit instantiation may be an
|
||
obstacle to any future plan to let exceptions be new-style
|
||
objects.)
|
||
|
||
In short, implicit instantiation has no advantages other than
|
||
backwards compatibility, and so should be phased out along with what
|
||
it exists to ensure compatibility with, namely, string exceptions.
|
||
|
||
|
||
Specification
|
||
=============
|
||
|
||
The syntax of ``raise_stmt`` [3]_ is to be changed from ::
|
||
|
||
raise_stmt ::= "raise" [expression ["," expression ["," expression]]]
|
||
|
||
to ::
|
||
|
||
raise_stmt ::= "raise" [expression ["," expression]]
|
||
|
||
If no expressions are present, the ``raise`` statement behaves as it
|
||
does presently: it re-raises the last exception that was active in the
|
||
current scope, and if no exception has been active in the current
|
||
scope, a ``TypeError`` is raised indicating that this is the problem.
|
||
|
||
Otherwise, the first expression is evaluated, producing the *raised
|
||
object*. Then the second expression is evaluated, if present,
|
||
producing the *substituted traceback*. If no second expression is
|
||
present, the substituted traceback is ``None``.
|
||
|
||
The raised object must be an instance. The class of the instance is
|
||
the exception type, and the instance itself is the exception value.
|
||
If the raised object is not an instance -- for example, if it is a
|
||
class or string -- a ``TypeError`` is raised.
|
||
|
||
If the substituted traceback is not ``None``, it must be a traceback
|
||
object, and it is substituted instead of the current location as the
|
||
place where the exception occurred. If it is neither a traceback
|
||
object nor ``None``, a ``TypeError`` is raised.
|
||
|
||
|
||
Backwards Compatibility
|
||
=======================
|
||
|
||
Migration Plan
|
||
--------------
|
||
|
||
Future Statement
|
||
''''''''''''''''
|
||
|
||
Under the :pep:`236` future statement::
|
||
|
||
from __future__ import raise_with_two_args
|
||
|
||
the syntax and semantics of the ``raise`` statement will be as
|
||
described above. This future feature is to appear in Python 2.4; its
|
||
effect is to become standard in Python 3.0.
|
||
|
||
As the examples below illustrate, this future statement is only needed
|
||
for code which uses the substituted traceback argument to ``raise``;
|
||
simple exception raising does not require it.
|
||
|
||
|
||
Warnings
|
||
''''''''
|
||
|
||
Three new :pep:`warnings <230>`, all of category ``DeprecationWarning``, are
|
||
to be issued to point out uses of ``raise`` which will become
|
||
incorrect under the proposed changes.
|
||
|
||
The first warning is issued when a ``raise`` statement is executed in
|
||
which the first expression evaluates to a string. The message for
|
||
this warning is::
|
||
|
||
raising strings will be impossible in the future
|
||
|
||
The second warning is issued when a ``raise`` statement is executed in
|
||
which the first expression evaluates to a class. The message for this
|
||
warning is::
|
||
|
||
raising classes will be impossible in the future
|
||
|
||
The third warning is issued when a ``raise`` statement with three
|
||
expressions is compiled. (Not, note, when it is executed; this is
|
||
important because the ``SyntaxError`` which this warning presages will
|
||
occur at compile-time.) The message for this warning is::
|
||
|
||
raising with three arguments will be impossible in the future
|
||
|
||
These warnings are to appear in Python 2.4, and disappear in Python
|
||
3.0, when the conditions which cause them are simply errors.
|
||
|
||
|
||
Examples
|
||
--------
|
||
|
||
Code Using Implicit Instantiation
|
||
'''''''''''''''''''''''''''''''''
|
||
|
||
Code such as ::
|
||
|
||
class MyError(Exception):
|
||
pass
|
||
|
||
raise MyError, 'spam'
|
||
|
||
will issue a warning when the ``raise`` statement is executed. The
|
||
``raise`` statement should be changed to instantiate explicitly::
|
||
|
||
raise MyError('spam')
|
||
|
||
|
||
Code Using String Exceptions
|
||
''''''''''''''''''''''''''''
|
||
|
||
Code such as ::
|
||
|
||
MyError = 'spam'
|
||
raise MyError, 'eggs'
|
||
|
||
will issue a warning when the ``raise`` statement is executed. The
|
||
exception type should be changed to a class::
|
||
|
||
class MyError(Exception):
|
||
pass
|
||
|
||
and, as in the previous example, the ``raise`` statement should be
|
||
changed to instantiate explicitly ::
|
||
|
||
raise MyError('eggs')
|
||
|
||
|
||
Code Supplying a Traceback Object
|
||
'''''''''''''''''''''''''''''''''
|
||
|
||
Code such as ::
|
||
|
||
raise MyError, 'spam', mytraceback
|
||
|
||
will issue a warning when compiled. The statement should be changed
|
||
to ::
|
||
|
||
raise MyError('spam'), mytraceback
|
||
|
||
and the future statement ::
|
||
|
||
from __future__ import raise_with_two_args
|
||
|
||
should be added at the top of the module. Note that adding this
|
||
future statement also turns the other two warnings into errors, so the
|
||
changes described in the previous examples must also be applied.
|
||
|
||
The special case ::
|
||
|
||
raise sys.exc_type, sys.exc_info, sys.exc_traceback
|
||
|
||
(which is intended to re-raise a previous exception) should be changed
|
||
simply to ::
|
||
|
||
raise
|
||
|
||
|
||
A Failure of the Plan
|
||
'''''''''''''''''''''
|
||
|
||
It may occur that a ``raise`` statement which raises a string or
|
||
implicitly instantiates is not executed in production or testing
|
||
during the phase-in period for this PEP. In that case, it will not
|
||
issue any warnings, but will instead suddenly fail one day in Python
|
||
3.0 or a subsequent version. (The failure is that the wrong exception
|
||
gets raised, namely a ``TypeError`` complaining about the arguments to
|
||
``raise``, instead of the exception intended.)
|
||
|
||
Such cases can be made rarer by prolonging the phase-in period; they
|
||
cannot be made impossible short of issuing at compile-time a warning
|
||
for every ``raise`` statement.
|
||
|
||
|
||
Rejection
|
||
=========
|
||
|
||
If this PEP were accepted, nearly all existing Python code would need
|
||
to be reviewed and probably revised; even if all the above arguments
|
||
in favour of explicit instantiation are accepted, the improvement in
|
||
clarity is too minor to justify the cost of doing the revision and the
|
||
risk of new bugs introduced thereby.
|
||
|
||
This proposal has therefore been rejected [6]_.
|
||
|
||
Note that string exceptions are slated for removal independently of
|
||
this proposal; what is rejected is the removal of implicit exception
|
||
instantiation.
|
||
|
||
|
||
Summary of Discussion
|
||
=====================
|
||
|
||
A small minority of respondents were in favour of the proposal, but
|
||
the dominant response was that any such migration would be costly
|
||
out of proportion to the putative benefit. As noted above, this
|
||
point is sufficient in itself to reject the PEP.
|
||
|
||
|
||
New-Style Exceptions
|
||
--------------------
|
||
|
||
Implicit instantiation might conflict with future plans to allow
|
||
instances of new-style classes to be used as exceptions. In order to
|
||
decide whether to instantiate implicitly, the ``raise`` machinery must
|
||
determine whether the first argument is a class or an instance -- but
|
||
with new-style classes there is no clear and strong distinction.
|
||
|
||
Under this proposal, the problem would be avoided because the
|
||
exception would already have been instantiated. However, there are
|
||
two plausible alternative solutions:
|
||
|
||
1. Require exception types to be subclasses of ``Exception``, and
|
||
instantiate implicitly if and only if ::
|
||
|
||
issubclass(firstarg, Exception)
|
||
|
||
2. Instantiate implicitly if and only if ::
|
||
|
||
isinstance(firstarg, type)
|
||
|
||
Thus eliminating implicit instantiation entirely is not necessary to
|
||
solve this problem.
|
||
|
||
|
||
Ugliness of Explicit Instantiation
|
||
----------------------------------
|
||
|
||
Some respondents felt that the explicitly instantiating syntax is
|
||
uglier, especially in cases when no arguments are supplied to the
|
||
exception constructor::
|
||
|
||
raise TypeError()
|
||
|
||
The problem is particularly acute when the exception instance itself
|
||
is not of interest, that is, when the only relevant point is the
|
||
exception type::
|
||
|
||
try:
|
||
# ... deeply nested search loop ...
|
||
raise Found
|
||
except Found:
|
||
# ...
|
||
|
||
In such cases the symmetry between ``raise`` and ``except`` can be
|
||
more expressive of the intent of the code.
|
||
|
||
Guido opined that the implicitly instantiating syntax is "a tad
|
||
prettier" even for cases with a single argument, since it has less
|
||
punctuation.
|
||
|
||
|
||
Performance Penalty of Warnings
|
||
-------------------------------
|
||
|
||
Experience with deprecating ``apply()`` shows that use of the warning
|
||
framework can incur a significant performance penalty.
|
||
|
||
Code which instantiates explicitly would not be affected, since the
|
||
run-time checks necessary to determine whether to issue a warning are
|
||
exactly those which are needed to determine whether to instantiate
|
||
implicitly in the first place. That is, such statements are already
|
||
incurring the cost of these checks.
|
||
|
||
Code which instantiates implicitly would incur a large cost: timing
|
||
trials indicate that issuing a warning (whether it is suppressed or
|
||
not) takes about five times more time than simply instantiating,
|
||
raising, and catching an exception.
|
||
|
||
This penalty is mitigated by the fact that ``raise`` statements are
|
||
rarely on performance-critical execution paths.
|
||
|
||
|
||
Traceback Argument
|
||
------------------
|
||
|
||
As the proposal stands, it would be impossible to use the traceback
|
||
argument to ``raise`` conveniently with all 2.x versions of Python.
|
||
|
||
For compatibility with versions < 2.4, the three-argument form must be
|
||
used; but this form would produce warnings with versions >= 2.4.
|
||
Those warnings could be suppressed, but doing so is awkward because
|
||
the relevant type of warning is issued at compile-time.
|
||
|
||
If this PEP were still under consideration, this objection would be
|
||
met by extending the phase-in period. For example, warnings could
|
||
first be issued in 3.0, and become errors in some later release.
|
||
|
||
|
||
References
|
||
==========
|
||
|
||
.. [1] "Standard Exception Classes in Python 1.5", Guido van Rossum.
|
||
http://www.python.org/doc/essays/stdexceptions.html
|
||
|
||
.. [3] "Python Language Reference", Guido van Rossum.
|
||
http://docs.python.org/reference/simple_stmts.html#raise
|
||
|
||
.. [6] Guido van Rossum, 11 June 2003 post to ``python-dev``.
|
||
https://mail.python.org/pipermail/python-dev/2003-June/036176.html
|
||
|
||
|
||
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:
|