Eliminate Implicit Exception Instantiation, by Steven Taschuk
This commit is contained in:
parent
613e70e3b8
commit
9981a257a3
|
@ -0,0 +1,350 @@
|
||||||
|
PEP: 317
|
||||||
|
Title: Eliminate Implicit Exception Instantiation
|
||||||
|
Version: $Revision$
|
||||||
|
Last-Modified: $Date$
|
||||||
|
Author: Steven Taschuk <staschuk@telusplanet.net>
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Content-Type: text/x-rst
|
||||||
|
Created: 06-May-2003
|
||||||
|
Python-Version: 2.4
|
||||||
|
Post-History:
|
||||||
|
|
||||||
|
|
||||||
|
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 [2]_.)
|
||||||
|
|
||||||
|
|
||||||
|
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 future statement [4]_ ::
|
||||||
|
|
||||||
|
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 warnings [5]_, 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.
|
||||||
|
|
||||||
|
|
||||||
|
References
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. [1] "Standard Exception Classes in Python 1.5", Guido van Rossum.
|
||||||
|
http://www.python.org/doc/essays/stdexceptions.html
|
||||||
|
|
||||||
|
.. [2] "Guidelines for Language Evolution", Paul Prescod.
|
||||||
|
http://www.python.org/peps/pep-0005.html
|
||||||
|
|
||||||
|
.. [3] "Python Language Reference", Guido van Rossum.
|
||||||
|
http://www.python.org/doc/current/ref/raise.html
|
||||||
|
|
||||||
|
.. [4] PEP 236 "Back to the __future__", Tim Peters.
|
||||||
|
http://www.python.org/peps/pep-0236.html
|
||||||
|
|
||||||
|
.. [5] PEP 230 "Warning Framework", Guido van Rossum.
|
||||||
|
http://www.python.org/peps/pep-0230.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:
|
Loading…
Reference in New Issue