Permalink
Cannot retrieve contributors at this time
465 lines (315 sloc)
14.5 KB
| 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 [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. | |
| 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 | |
| .. [2] "Guidelines for Language Evolution", Paul Prescod. | |
| http://www.python.org/dev/peps/pep-0005/ | |
| .. [3] "Python Language Reference", Guido van Rossum. | |
| http://docs.python.org/reference/simple_stmts.html#raise | |
| .. [4] PEP 236 "Back to the __future__", Tim Peters. | |
| http://www.python.org/dev/peps/pep-0236/ | |
| .. [5] PEP 230 "Warning Framework", Guido van Rossum. | |
| http://www.python.org/dev/peps/pep-0230/ | |
| .. [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: |