reSTify 5 PEPs (#276)
This commit is contained in:
parent
02969ebbd2
commit
329ed7e935
412
pep-0236.txt
412
pep-0236.txt
|
@ -5,80 +5,84 @@ Last-Modified: $Date$
|
|||
Author: Tim Peters <tim.peters@gmail.com>
|
||||
Status: Final
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 26-Feb-2001
|
||||
Python-Version: 2.1
|
||||
Post-History: 26-Feb-2001
|
||||
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
From time to time, Python makes an incompatible change to the
|
||||
advertised semantics of core language constructs, or changes their
|
||||
accidental (implementation-dependent) behavior in some way. While this
|
||||
is never done capriciously, and is always done with the aim of
|
||||
improving the language over the long term, over the short term it's
|
||||
contentious and disrupting.
|
||||
From time to time, Python makes an incompatible change to the advertised
|
||||
semantics of core language constructs, or changes their accidental
|
||||
(implementation-dependent) behavior in some way. While this is never done
|
||||
capriciously, and is always done with the aim of improving the language over
|
||||
the long term, over the short term it's contentious and disrupting.
|
||||
|
||||
PEP 5, Guidelines for Language Evolution[1] suggests ways to ease
|
||||
the pain, and this PEP introduces some machinery in support of that.
|
||||
PEP 5, Guidelines for Language Evolution [1]_ suggests ways to ease the pain,
|
||||
and this PEP introduces some machinery in support of that.
|
||||
|
||||
PEP 227, Statically Nested Scopes[2] is the first application, and
|
||||
will be used as an example here.
|
||||
PEP 227, Statically Nested Scopes [2]_ is the first application, and will be
|
||||
used as an example here.
|
||||
|
||||
|
||||
Intent
|
||||
======
|
||||
|
||||
[Note: This is policy, and so should eventually move into PEP 5 [1]]
|
||||
[Note: This is policy, and so should eventually move into PEP 5 [1]_]
|
||||
|
||||
When an incompatible change to core language syntax or semantics is
|
||||
being made:
|
||||
When an incompatible change to core language syntax or semantics is being
|
||||
made:
|
||||
|
||||
1. The release C that introduces the change does not change the
|
||||
syntax or semantics by default.
|
||||
1. The release C that introduces the change does not change the syntax or
|
||||
semantics by default.
|
||||
|
||||
2. A future release R is identified in which the new syntax or semantics
|
||||
will be enforced.
|
||||
2. A future release R is identified in which the new syntax or semantics will
|
||||
be enforced.
|
||||
|
||||
3. The mechanisms described in PEP 3, Warning Framework[3] are
|
||||
used to generate warnings, whenever possible, about constructs
|
||||
or operations whose meaning may[4] change in release R.
|
||||
3. The mechanisms described in PEP 3, Warning Framework [3]_ are used to
|
||||
generate warnings, whenever possible, about constructs or operations whose
|
||||
meaning may [4]_ change in release R.
|
||||
|
||||
4. The new future_statement (see below) can be explicitly included in a
|
||||
module M to request that the code in module M use the new syntax or
|
||||
semantics in the current release C.
|
||||
4. The new future_statement (see below) can be explicitly included in a module
|
||||
M to request that the code in module M use the new syntax or semantics in
|
||||
the current release C.
|
||||
|
||||
So old code continues to work by default, for at least one release,
|
||||
although it may start to generate new warning messages. Migration to
|
||||
the new syntax or semantics can proceed during that time, using the
|
||||
future_statement to make modules containing it act as if the new syntax
|
||||
or semantics were already being enforced.
|
||||
So old code continues to work by default, for at least one release, although
|
||||
it may start to generate new warning messages. Migration to the new syntax or
|
||||
semantics can proceed during that time, using the future_statement to make
|
||||
modules containing it act as if the new syntax or semantics were already being
|
||||
enforced.
|
||||
|
||||
Note that there is no need to involve the future_statement machinery
|
||||
in new features unless they can break existing code; fully backward-
|
||||
compatible additions can-- and should --be introduced without a
|
||||
corresponding future_statement.
|
||||
Note that there is no need to involve the future_statement machinery in new
|
||||
features unless they can break existing code; fully backward- compatible
|
||||
additions can-- and should --be introduced without a corresponding
|
||||
future_statement.
|
||||
|
||||
|
||||
Syntax
|
||||
======
|
||||
|
||||
A future_statement is simply a from/import statement using the reserved
|
||||
module name __future__:
|
||||
A future_statement is simply a from/import statement using the reserved module
|
||||
name ``__future__``::
|
||||
|
||||
future_statement: "from" "__future__" "import" feature ["as" name]
|
||||
("," feature ["as" name])*
|
||||
(","feature ["as" name])*
|
||||
|
||||
feature: identifier
|
||||
name: identifier
|
||||
|
||||
In addition, all future_statments must appear near the top of the
|
||||
module. The only lines that can appear before a future_statement are:
|
||||
In addition, all future_statments must appear near the top of the module. The
|
||||
only lines that can appear before a future_statement are:
|
||||
|
||||
+ The module docstring (if any).
|
||||
+ Comments.
|
||||
+ Blank lines.
|
||||
+ Other future_statements.
|
||||
+ The module docstring (if any).
|
||||
+ Comments.
|
||||
+ Blank lines.
|
||||
+ Other future_statements.
|
||||
|
||||
Example::
|
||||
|
||||
Example:
|
||||
"""This is a module docstring."""
|
||||
|
||||
# This is a comment, preceded by a blank line and followed by
|
||||
|
@ -91,37 +95,38 @@ Syntax
|
|||
|
||||
|
||||
Semantics
|
||||
=========
|
||||
|
||||
A future_statement is recognized and treated specially at compile time:
|
||||
changes to the semantics of core constructs are often implemented by
|
||||
generating different code. It may even be the case that a new feature
|
||||
introduces new incompatible syntax (such as a new reserved word), in
|
||||
which case the compiler may need to parse the module differently. Such
|
||||
decisions cannot be pushed off until runtime.
|
||||
A future_statement is recognized and treated specially at compile time:
|
||||
changes to the semantics of core constructs are often implemented by
|
||||
generating different code. It may even be the case that a new feature
|
||||
introduces new incompatible syntax (such as a new reserved word), in which
|
||||
case the compiler may need to parse the module differently. Such decisions
|
||||
cannot be pushed off until runtime.
|
||||
|
||||
For any given release, the compiler knows which feature names have been
|
||||
defined, and raises a compile-time error if a future_statement contains
|
||||
a feature not known to it[5].
|
||||
For any given release, the compiler knows which feature names have been
|
||||
defined, and raises a compile-time error if a future_statement contains a
|
||||
feature not known to it [5]_.
|
||||
|
||||
The direct runtime semantics are the same as for any import statement:
|
||||
there is a standard module __future__.py, described later, and it will
|
||||
be imported in the usual way at the time the future_statement is
|
||||
executed.
|
||||
The direct runtime semantics are the same as for any ``import`` statement:
|
||||
there is a standard module ``__future__.py``, described later, and it will be
|
||||
imported in the usual way at the time the future_statement is executed.
|
||||
|
||||
The *interesting* runtime semantics depend on the specific feature(s)
|
||||
"imported" by the future_statement(s) appearing in the module.
|
||||
The *interesting* runtime semantics depend on the specific feature(s)
|
||||
"imported" by the future_statement(s) appearing in the module.
|
||||
|
||||
Note that there is nothing special about the statement:
|
||||
Note that there is nothing special about the statement::
|
||||
|
||||
import __future__ [as name]
|
||||
|
||||
That is not a future_statement; it's an ordinary import statement, with
|
||||
no special semantics or syntax restrictions.
|
||||
That is not a future_statement; it's an ordinary import statement, with no
|
||||
special semantics or syntax restrictions.
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
Consider this code, in file scope.py:
|
||||
Consider this code, in file scope.py::
|
||||
|
||||
x = 42
|
||||
def f():
|
||||
|
@ -131,243 +136,244 @@ Example
|
|||
g()
|
||||
f()
|
||||
|
||||
Under 2.0, it prints:
|
||||
Under 2.0, it prints::
|
||||
|
||||
x is 42
|
||||
|
||||
Nested scopes[2] are being introduced in 2.1. But under 2.1, it still
|
||||
prints
|
||||
Nested scopes [2]_ are being introduced in 2.1. But under 2.1, it still
|
||||
prints::
|
||||
|
||||
x is 42
|
||||
|
||||
and also generates a warning.
|
||||
and also generates a warning.
|
||||
|
||||
In 2.2, and also in 2.1 *if* "from __future__ import nested_scopes" is
|
||||
included at the top of scope.py, it prints
|
||||
In 2.2, and also in 2.1 *if* ``from __future__ import nested_scopes`` is
|
||||
included at the top of ``scope.py``, it prints::
|
||||
|
||||
x is 666
|
||||
|
||||
|
||||
Standard Module __future__.py
|
||||
=============================
|
||||
|
||||
Lib/__future__.py is a real module, and serves three purposes:
|
||||
``Lib/__future__.py`` is a real module, and serves three purposes:
|
||||
|
||||
1. To avoid confusing existing tools that analyze import statements and
|
||||
expect to find the modules they're importing.
|
||||
1. To avoid confusing existing tools that analyze import statements and expect
|
||||
to find the modules they're importing.
|
||||
|
||||
2. To ensure that future_statements run under releases prior to 2.1
|
||||
at least yield runtime exceptions (the import of __future__ will
|
||||
fail, because there was no module of that name prior to 2.1).
|
||||
2. To ensure that future_statements run under releases prior to 2.1 at least
|
||||
yield runtime exceptions (the import of ``__future__`` will fail, because
|
||||
there was no module of that name prior to 2.1).
|
||||
|
||||
3. To document when incompatible changes were introduced, and when they
|
||||
will be-- or were --made mandatory. This is a form of executable
|
||||
documentation, and can be inspected programatically via importing
|
||||
__future__ and examining its contents.
|
||||
3. To document when incompatible changes were introduced, and when they will
|
||||
be-- or were --made mandatory. This is a form of executable documentation,
|
||||
and can be inspected programatically via importing ``__future__`` and
|
||||
examining its contents.
|
||||
|
||||
Each statement in __future__.py is of the form:
|
||||
Each statement in ``__future__.py`` is of the form::
|
||||
|
||||
FeatureName = "_Feature(" OptionalRelease "," MandatoryRelease ")"
|
||||
|
||||
where, normally, OptionalRelease < MandatoryRelease, and both are
|
||||
5-tuples of the same form as sys.version_info:
|
||||
where, normally, *OptionalRelease* < *MandatoryRelease*, and both are
|
||||
5-tuples of the same form as ``sys.version_info``::
|
||||
|
||||
(PY_MAJOR_VERSION, # the 2 in 2.1.0a3; an int
|
||||
PY_MINOR_VERSION, # the 1; an int
|
||||
PY_MICRO_VERSION, # the 0; an int
|
||||
PY_RELEASE_LEVEL, # "alpha", "beta", "candidate" or "final"; string
|
||||
PY_RELEASE_SERIAL # the 3; an int
|
||||
)
|
||||
PY_RELEASE_SERIAL # the 3; an int )
|
||||
|
||||
OptionalRelease records the first release in which
|
||||
*OptionalRelease* records the first release in which::
|
||||
|
||||
from __future__ import FeatureName
|
||||
|
||||
was accepted.
|
||||
was accepted.
|
||||
|
||||
In the case of MandatoryReleases that have not yet occurred,
|
||||
MandatoryRelease predicts the release in which the feature will become
|
||||
part of the language.
|
||||
In the case of *MandatoryReleases* that have not yet occurred,
|
||||
*MandatoryRelease* predicts the release in which the feature will become part
|
||||
of the language.
|
||||
|
||||
Else MandatoryRelease records when the feature became part of the
|
||||
language; in releases at or after that, modules no longer need
|
||||
Else *MandatoryRelease* records when the feature became part of the language;
|
||||
in releases at or after that, modules no longer need::
|
||||
|
||||
from __future__ import FeatureName
|
||||
|
||||
to use the feature in question, but may continue to use such imports.
|
||||
to use the feature in question, but may continue to use such imports.
|
||||
|
||||
MandatoryRelease may also be None, meaning that a planned feature got
|
||||
dropped.
|
||||
*MandatoryRelease* may also be ``None``, meaning that a planned feature got
|
||||
dropped.
|
||||
|
||||
Instances of class _Feature have two corresponding methods,
|
||||
.getOptionalRelease() and .getMandatoryRelease().
|
||||
Instances of ``class _Feature`` have two corresponding methods,
|
||||
``.getOptionalRelease()`` and ``.getMandatoryRelease()``.
|
||||
|
||||
No feature line will ever be deleted from __future__.py.
|
||||
No feature line will ever be deleted from ``__future__.py``.
|
||||
|
||||
Example line:
|
||||
Example line::
|
||||
|
||||
nested_scopes = _Feature((2, 1, 0, "beta", 1), (2, 2, 0, "final", 0))
|
||||
|
||||
This means that
|
||||
This means that::
|
||||
|
||||
from __future__ import nested_scopes
|
||||
|
||||
will work in all releases at or after 2.1b1, and that nested_scopes are
|
||||
intended to be enforced starting in release 2.2.
|
||||
will work in all releases at or after 2.1b1, and that nested_scopes are
|
||||
intended to be enforced starting in release 2.2.
|
||||
|
||||
|
||||
Resolved Problem: Runtime Compilation
|
||||
======================================
|
||||
|
||||
Several Python features can compile code during a module's runtime:
|
||||
Several Python features can compile code during a module's runtime:
|
||||
|
||||
1. The exec statement.
|
||||
2. The execfile() function.
|
||||
3. The compile() function.
|
||||
4. The eval() function.
|
||||
5. The input() function.
|
||||
1. The ``exec`` statement.
|
||||
2. The ``execfile()`` function.
|
||||
3. The ``compile()`` function.
|
||||
4. The ``eval()`` function.
|
||||
5. The ``input()`` function.
|
||||
|
||||
Since a module M containing a future_statement naming feature F
|
||||
explicitly requests that the current release act like a future release
|
||||
with respect to F, any code compiled dynamically from text passed to
|
||||
one of these from within M should probably also use the new syntax or
|
||||
semantics associated with F. The 2.1 release does behave this way.
|
||||
Since a module M containing a future_statement naming feature F explicitly
|
||||
requests that the current release act like a future release with respect to F,
|
||||
any code compiled dynamically from text passed to one of these from within M
|
||||
should probably also use the new syntax or semantics associated with F. The
|
||||
2.1 release does behave this way.
|
||||
|
||||
This isn't always desired, though. For example, doctest.testmod(M)
|
||||
compiles examples taken from strings in M, and those examples should
|
||||
use M's choices, not necessarily the doctest module's choices. In the
|
||||
2.1 release, this isn't possible, and no scheme has yet been suggested
|
||||
for working around this. NOTE: PEP 264 later addressed this in a
|
||||
flexible way, by adding optional arguments to compile().
|
||||
This isn't always desired, though. For example, ``doctest.testmod(M)``
|
||||
compiles examples taken from strings in M, and those examples should use M's
|
||||
choices, not necessarily the doctest module's choices. In the 2.1 release,
|
||||
this isn't possible, and no scheme has yet been suggested for working around
|
||||
this. NOTE: PEP 264 later addressed this in a flexible way, by adding
|
||||
optional arguments to ``compile()``.
|
||||
|
||||
In any case, a future_statement appearing "near the top" (see Syntax
|
||||
above) of text compiled dynamically by an exec, execfile() or compile()
|
||||
applies to the code block generated, but has no further effect on the
|
||||
module that executes such an exec, execfile() or compile(). This
|
||||
can't be used to affect eval() or input(), however, because they only
|
||||
allow expression input, and a future_statement is not an expression.
|
||||
In any case, a future_statement appearing "near the top" (see Syntax above) of
|
||||
text compiled dynamically by an ``exec``, ``execfile()`` or ``compile()``
|
||||
applies to the code block generated, but has no further effect on the module
|
||||
that executes such an ``exec``, ``execfile()`` or ``compile()``. This can't
|
||||
be used to affect ``eval()`` or ``input()``, however, because they only allow
|
||||
expression input, and a future_statement is not an expression.
|
||||
|
||||
|
||||
Resolved Problem: Native Interactive Shells
|
||||
============================================
|
||||
|
||||
There are two ways to get an interactive shell:
|
||||
There are two ways to get an interactive shell:
|
||||
|
||||
1. By invoking Python from a command line without a script argument.
|
||||
1. By invoking Python from a command line without a script argument.
|
||||
|
||||
2. By invoking Python from a command line with the -i switch and with a
|
||||
2. By invoking Python from a command line with the ``-i`` switch and with a
|
||||
script argument.
|
||||
|
||||
An interactive shell can be seen as an extreme case of runtime
|
||||
compilation (see above): in effect, each statement typed at an
|
||||
interactive shell prompt runs a new instance of exec, compile() or
|
||||
execfile(). A future_statement typed at an interactive shell applies to
|
||||
the rest of the shell session's life, as if the future_statement had
|
||||
appeared at the top of a module.
|
||||
An interactive shell can be seen as an extreme case of runtime compilation
|
||||
(see above): in effect, each statement typed at an interactive shell prompt
|
||||
runs a new instance of ``exec``, ``compile()`` or ``execfile()``. A
|
||||
future_statement typed at an interactive shell applies to the rest of the
|
||||
shell session's life, as if the future_statement had appeared at the top of a
|
||||
module.
|
||||
|
||||
|
||||
Resolved Problem: Simulated Interactive Shells
|
||||
===============================================
|
||||
|
||||
Interactive shells "built by hand" (by tools such as IDLE and the Emacs
|
||||
Python-mode) should behave like native interactive shells (see above).
|
||||
However, the machinery used internally by native interactive shells has
|
||||
not been exposed, and there isn't a clear way for tools building their
|
||||
own interactive shells to achieve the desired behavior.
|
||||
Interactive shells "built by hand" (by tools such as IDLE and the Emacs
|
||||
Python-mode) should behave like native interactive shells (see above).
|
||||
However, the machinery used internally by native interactive shells has not
|
||||
been exposed, and there isn't a clear way for tools building their own
|
||||
interactive shells to achieve the desired behavior.
|
||||
|
||||
NOTE: PEP 264 later addressed this, by adding intelligence to the
|
||||
standard codeop.py. Simulated shells that don't use the standard
|
||||
library shell helpers can get a similar effect by exploiting the
|
||||
new optional arguments to compile() added by PEP 264.
|
||||
NOTE: PEP 264 later addressed this, by adding intelligence to the standard
|
||||
``codeop.py``. Simulated shells that don't use the standard library shell
|
||||
helpers can get a similar effect by exploiting the new optional arguments to
|
||||
``compile()`` added by PEP 264.
|
||||
|
||||
|
||||
Questions and Answers
|
||||
=====================
|
||||
|
||||
Q: What about a "from __past__" version, to get back *old* behavior?
|
||||
What about a "from __past__" version, to get back *old* behavior?
|
||||
-----------------------------------------------------------------
|
||||
|
||||
A: Outside the scope of this PEP. Seems unlikely to the author,
|
||||
though. Write a PEP if you want to pursue it.
|
||||
Outside the scope of this PEP. Seems unlikely to the author, though. Write a
|
||||
PEP if you want to pursue it.
|
||||
|
||||
Q: What about incompatibilities due to changes in the Python virtual
|
||||
machine?
|
||||
What about incompatibilities due to changes in the Python virtual machine?
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
A: Outside the scope of this PEP, although PEP 5 [1] suggests a grace
|
||||
period there too, and the future_statement may also have a role to
|
||||
play there.
|
||||
Outside the scope of this PEP, although PEP 5 [1]_ suggests a grace period
|
||||
there too, and the future_statement may also have a role to play there.
|
||||
|
||||
Q: What about incompatibilities due to changes in Python's C API?
|
||||
What about incompatibilities due to changes in Python's C API?
|
||||
--------------------------------------------------------------
|
||||
|
||||
A: Outside the scope of this PEP.
|
||||
Outside the scope of this PEP.
|
||||
|
||||
Q: I want to wrap future_statements in try/except blocks, so I can
|
||||
use different code depending on which version of Python I'm running.
|
||||
Why can't I?
|
||||
I want to wrap future_statements in try/except blocks, so I can use different code depending on which version of Python I'm running. Why can't I?
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
A: Sorry! try/except is a runtime feature; future_statements are
|
||||
primarily compile-time gimmicks, and your try/except happens long
|
||||
after the compiler is done. That is, by the time you do
|
||||
try/except, the semantics in effect for the module are already a
|
||||
done deal. Since the try/except wouldn't accomplish what it
|
||||
*looks* like it should accomplish, it's simply not allowed. We
|
||||
also want to keep these special statements very easy to find and to
|
||||
recognize.
|
||||
Sorry! ``try/except`` is a runtime feature; future_statements are primarily
|
||||
compile-time gimmicks, and your ``try/except`` happens long after the compiler
|
||||
is done. That is, by the time you do ``try/except``, the semantics in effect
|
||||
for the module are already a done deal. Since the ``try/except`` wouldn't
|
||||
accomplish what it *looks* like it should accomplish, it's simply not allowed.
|
||||
We also want to keep these special statements very easy to find and to
|
||||
recognize.
|
||||
|
||||
Note that you *can* import __future__ directly, and use the
|
||||
information in it, along with sys.version_info, to figure out where
|
||||
the release you're running under stands in relation to a given
|
||||
feature's status.
|
||||
Note that you *can* import ``__future__`` directly, and use the information in
|
||||
it, along with ``sys.version_info``, to figure out where the release you're
|
||||
running under stands in relation to a given feature's status.
|
||||
|
||||
Q: Going back to the nested_scopes example, what if release 2.2 comes
|
||||
along and I still haven't changed my code? How can I keep the 2.1
|
||||
behavior then?
|
||||
Going back to the nested_scopes example, what if release 2.2 comes along and I still haven't changed my code? How can I keep the 2.1 behavior then?
|
||||
----------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
A: By continuing to use 2.1, and not moving to 2.2 until you do change
|
||||
your code. The purpose of future_statement is to make life easier
|
||||
for people who keep current with the latest release in a timely
|
||||
fashion. We don't hate you if you don't, but your problems are
|
||||
much harder to solve, and somebody with those problems will need to
|
||||
write a PEP addressing them. future_statement is aimed at a
|
||||
different audience.
|
||||
By continuing to use 2.1, and not moving to 2.2 until you do change your
|
||||
code. The purpose of future_statement is to make life easier for people who
|
||||
keep current with the latest release in a timely fashion. We don't hate you
|
||||
if you don't, but your problems are much harder to solve, and somebody with
|
||||
those problems will need to write a PEP addressing them. future_statement is
|
||||
aimed at a different audience.
|
||||
|
||||
Q: Overloading "import" sucks. Why not introduce a new statement for
|
||||
this?
|
||||
Overloading ``import`` sucks. Why not introduce a new statement for this?
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
A: Like maybe "lambda lambda nested_scopes"? That is, unless we
|
||||
introduce a new keyword, we can't introduce an entirely new
|
||||
statement. But if we introduce a new keyword, that in itself
|
||||
would break old code. That would be too ironic to bear. Yes,
|
||||
overloading "import" does suck, but not as energetically as the
|
||||
alternatives -- as is, future_statements are 100% backward
|
||||
compatible.
|
||||
Like maybe ``lambda lambda nested_scopes``? That is, unless we introduce a
|
||||
new keyword, we can't introduce an entirely new statement. But if we
|
||||
introduce a new keyword, that in itself would break old code. That would be
|
||||
too ironic to bear. Yes, overloading ``import`` does suck, but not as
|
||||
energetically as the alternatives -- as is, future_statements are 100%
|
||||
backward compatible.
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
References and Footnotes
|
||||
========================
|
||||
|
||||
[1] PEP 5, Guidelines for Language Evolution, Prescod
|
||||
.. [1] PEP 5, Guidelines for Language Evolution, Prescod
|
||||
http://www.python.org/dev/peps/pep-0005/
|
||||
|
||||
[2] PEP 227, Statically Nested Scopes, Hylton
|
||||
.. [2] PEP 227, Statically Nested Scopes, Hylton
|
||||
http://www.python.org/dev/peps/pep-0227/
|
||||
|
||||
[3] PEP 230, Warning Framework, Van Rossum
|
||||
.. [3] PEP 230, Warning Framework, Van Rossum
|
||||
http://www.python.org/dev/peps/pep-0230/
|
||||
|
||||
[4] Note that this is "may" and not "will": better safe than sorry. Of
|
||||
course spurious warnings won't be generated when avoidable with
|
||||
reasonable cost.
|
||||
.. [4] Note that this is *may* and not *will*: better safe than sorry. Of course
|
||||
spurious warnings won't be generated when avoidable with reasonable cost.
|
||||
|
||||
[5] This ensures that a future_statement run under a release prior to
|
||||
the first one in which a given feature is known (but >= 2.1) will
|
||||
raise a compile-time error rather than silently do a wrong thing.
|
||||
If transported to a release prior to 2.1, a runtime error will be
|
||||
raised because of the failure to import __future__ (no such module
|
||||
existed in the standard distribution before the 2.1 release, and
|
||||
the double underscores make it a reserved name).
|
||||
.. [5] This ensures that a future_statement run under a release prior to the
|
||||
first one in which a given feature is known (but >= 2.1) will raise a
|
||||
compile-time error rather than silently do a wrong thing. If transported
|
||||
to a release prior to 2.1, a runtime error will be raised because of the
|
||||
failure to import ``__future__`` (no such module existed in the standard
|
||||
distribution before the 2.1 release, and the double underscores make it a
|
||||
reserved name).
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
|
202
pep-0247.txt
202
pep-0247.txt
|
@ -5,104 +5,100 @@ Last-Modified: $Date$
|
|||
Author: A.M. Kuchling <amk@amk.ca>
|
||||
Status: Final
|
||||
Type: Informational
|
||||
Content-Type: text/x-rst
|
||||
Created: 23-Mar-2001
|
||||
Post-History: 20-Sep-2001
|
||||
|
||||
Abstract
|
||||
|
||||
There are several different modules available that implement
|
||||
cryptographic hashing algorithms such as MD5 or SHA. This
|
||||
document specifies a standard API for such algorithms, to make it
|
||||
easier to switch between different implementations.
|
||||
Abstract
|
||||
========
|
||||
|
||||
There are several different modules available that implement cryptographic
|
||||
hashing algorithms such as MD5 or SHA. This document specifies a standard API
|
||||
for such algorithms, to make it easier to switch between different
|
||||
implementations.
|
||||
|
||||
|
||||
Specification
|
||||
=============
|
||||
|
||||
All hashing modules should present the same interface. Additional
|
||||
methods or variables can be added, but those described in this
|
||||
document should always be present.
|
||||
All hashing modules should present the same interface. Additional methods or
|
||||
variables can be added, but those described in this document should always be
|
||||
present.
|
||||
|
||||
Hash function modules define one function:
|
||||
Hash function modules define one function:
|
||||
|
||||
new([string]) (unkeyed hashes)
|
||||
new([key] , [string]) (keyed hashes)
|
||||
| ``new([string]) (unkeyed hashes)``
|
||||
| ``new([key] , [string]) (keyed hashes)``
|
||||
|
||||
Create a new hashing object and return it. The first form is
|
||||
for hashes that are unkeyed, such as MD5 or SHA. For keyed
|
||||
hashes such as HMAC, 'key' is a required parameter containing
|
||||
a string giving the key to use. In both cases, the optional
|
||||
'string' parameter, if supplied, will be immediately hashed
|
||||
into the object's starting state, as if obj.update(string) was
|
||||
called.
|
||||
Create a new hashing object and return it. The first form is for hashes
|
||||
that are unkeyed, such as MD5 or SHA. For keyed hashes such as HMAC, *key*
|
||||
is a required parameter containing a string giving the key to use. In both
|
||||
cases, the optional *string* parameter, if supplied, will be immediately
|
||||
hashed into the object's starting state, as if ``obj.update(string)``
|
||||
was called.
|
||||
|
||||
After creating a hashing object, arbitrary strings can be fed
|
||||
into the object using its update() method, and the hash value
|
||||
can be obtained at any time by calling the object's digest()
|
||||
method.
|
||||
After creating a hashing object, arbitrary strings can be fed into the
|
||||
object using its ``update()`` method, and the hash value can be obtained at
|
||||
any time by calling the object's ``digest()`` method.
|
||||
|
||||
Arbitrary additional keyword arguments can be added to this
|
||||
function, but if they're not supplied, sensible default values
|
||||
should be used. For example, 'rounds' and 'digest_size'
|
||||
keywords could be added for a hash function which supports a
|
||||
variable number of rounds and several different output sizes,
|
||||
and they should default to values believed to be secure.
|
||||
Arbitrary additional keyword arguments can be added to this function, but if
|
||||
they're not supplied, sensible default values should be used. For example,
|
||||
``rounds`` and ``digest_size`` keywords could be added for a hash function
|
||||
which supports a variable number of rounds and several different output
|
||||
sizes, and they should default to values believed to be secure.
|
||||
|
||||
Hash function modules define one variable:
|
||||
Hash function modules define one variable:
|
||||
|
||||
digest_size
|
||||
| ``digest_size``
|
||||
|
||||
An integer value; the size of the digest produced by the
|
||||
hashing objects created by this module, measured in bytes.
|
||||
You could also obtain this value by creating a sample object
|
||||
and accessing its 'digest_size' attribute, but it can be
|
||||
convenient to have this value available from the module.
|
||||
Hashes with a variable output size will set this variable to
|
||||
None.
|
||||
An integer value; the size of the digest produced by the hashing objects
|
||||
created by this module, measured in bytes. You could also obtain this value
|
||||
by creating a sample object and accessing its ``digest_size`` attribute, but
|
||||
it can be convenient to have this value available from the module. Hashes
|
||||
with a variable output size will set this variable to ``None``.
|
||||
|
||||
Hashing objects require a single attribute:
|
||||
Hashing objects require a single attribute:
|
||||
|
||||
digest_size
|
||||
| ``digest_size``
|
||||
|
||||
This attribute is identical to the module-level digest_size
|
||||
variable, measuring the size of the digest produced by the
|
||||
hashing object, measured in bytes. If the hash has a variable
|
||||
output size, this output size must be chosen when the hashing
|
||||
object is created, and this attribute must contain the
|
||||
selected size. Therefore, None is *not* a legal value for this
|
||||
This attribute is identical to the module-level ``digest_size`` variable,
|
||||
measuring the size of the digest produced by the hashing object, measured in
|
||||
bytes. If the hash has a variable output size, this output size must be
|
||||
chosen when the hashing object is created, and this attribute must contain
|
||||
the selected size. Therefore, ``None`` is *not* a legal value for this
|
||||
attribute.
|
||||
|
||||
|
||||
Hashing objects require the following methods:
|
||||
Hashing objects require the following methods:
|
||||
|
||||
copy()
|
||||
| ``copy()``
|
||||
|
||||
Return a separate copy of this hashing object. An update to
|
||||
this copy won't affect the original object.
|
||||
Return a separate copy of this hashing object. An update to this copy won't
|
||||
affect the original object.
|
||||
|
||||
digest()
|
||||
| ``digest()``
|
||||
|
||||
Return the hash value of this hashing object as a string
|
||||
containing 8-bit data. The object is not altered in any way
|
||||
by this function; you can continue updating the object after
|
||||
calling this function.
|
||||
Return the hash value of this hashing object as a string containing 8-bit
|
||||
data. The object is not altered in any way by this function; you can
|
||||
continue updating the object after calling this function.
|
||||
|
||||
hexdigest()
|
||||
| ``hexdigest()``
|
||||
|
||||
Return the hash value of this hashing object as a string
|
||||
containing hexadecimal digits. Lowercase letters should be used
|
||||
for the digits 'a' through 'f'. Like the .digest() method, this
|
||||
method mustn't alter the object.
|
||||
Return the hash value of this hashing object as a string containing
|
||||
hexadecimal digits. Lowercase letters should be used for the digits ``a``
|
||||
through ``f``. Like the ``.digest()`` method, this method mustn't alter the
|
||||
object.
|
||||
|
||||
update(string)
|
||||
| ``update(string)``
|
||||
|
||||
Hash 'string' into the current state of the hashing object.
|
||||
update() can be called any number of times during a hashing
|
||||
object's lifetime.
|
||||
Hash *string* into the current state of the hashing object. ``update()`` can
|
||||
be called any number of times during a hashing object's lifetime.
|
||||
|
||||
Hashing modules can define additional module-level functions or
|
||||
object methods and still be compliant with this specification.
|
||||
Hashing modules can define additional module-level functions or object methods
|
||||
and still be compliant with this specification.
|
||||
|
||||
Here's an example, using a module named 'MD5':
|
||||
Here's an example, using a module named ``MD5``::
|
||||
|
||||
>>> from Crypto.Hash import MD5
|
||||
>>> m = MD5.new()
|
||||
|
@ -118,56 +114,60 @@ Specification
|
|||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
The digest size is measured in bytes, not bits, even though hash
|
||||
algorithm sizes are usually quoted in bits; MD5 is a 128-bit
|
||||
algorithm and not a 16-byte one, for example. This is because, in
|
||||
the sample code I looked at, the length in bytes is often needed
|
||||
(to seek ahead or behind in a file; to compute the length of an
|
||||
output string) while the length in bits is rarely used.
|
||||
Therefore, the burden will fall on the few people actually needing
|
||||
the size in bits, who will have to multiply digest_size by 8.
|
||||
The digest size is measured in bytes, not bits, even though hash algorithm
|
||||
sizes are usually quoted in bits; MD5 is a 128-bit algorithm and not a 16-byte
|
||||
one, for example. This is because, in the sample code I looked at, the length
|
||||
in bytes is often needed (to seek ahead or behind in a file; to compute the
|
||||
length of an output string) while the length in bits is rarely used. Therefore,
|
||||
the burden will fall on the few people actually needing the size in bits, who
|
||||
will have to multiply ``digest_size`` by 8.
|
||||
|
||||
It's been suggested that the update() method would be better named
|
||||
append(). However, that method is really causing the current
|
||||
state of the hashing object to be updated, and update() is already
|
||||
used by the md5 and sha modules included with Python, so it seems
|
||||
simplest to leave the name update() alone.
|
||||
It's been suggested that the ``update()`` method would be better named
|
||||
``append()``. However, that method is really causing the current state of the
|
||||
hashing object to be updated, and ``update()`` is already used by the md5 and
|
||||
sha modules included with Python, so it seems simplest to leave the name
|
||||
``update()`` alone.
|
||||
|
||||
The order of the constructor's arguments for keyed hashes was a
|
||||
sticky issue. It wasn't clear whether the key should come first
|
||||
or second. It's a required parameter, and the usual convention is
|
||||
to place required parameters first, but that also means that the
|
||||
'string' parameter moves from the first position to the second.
|
||||
It would be possible to get confused and pass a single argument to
|
||||
a keyed hash, thinking that you're passing an initial string to an
|
||||
unkeyed hash, but it doesn't seem worth making the interface
|
||||
for keyed hashes more obscure to avoid this potential error.
|
||||
The order of the constructor's arguments for keyed hashes was a sticky issue.
|
||||
It wasn't clear whether the *key* should come first or second. It's a required
|
||||
parameter, and the usual convention is to place required parameters first, but
|
||||
that also means that the *string* parameter moves from the first position to
|
||||
the second. It would be possible to get confused and pass a single argument to
|
||||
a keyed hash, thinking that you're passing an initial string to an unkeyed
|
||||
hash, but it doesn't seem worth making the interface for keyed hashes more
|
||||
obscure to avoid this potential error.
|
||||
|
||||
|
||||
Changes
|
||||
=======
|
||||
|
||||
2001-09-17: Renamed clear() to reset(); added digest_size attribute
|
||||
to objects; added .hexdigest() method.
|
||||
2001-09-20: Removed reset() method completely.
|
||||
2001-09-28: Set digest_size to None for variable-size hashes.
|
||||
2001-09-17: Renamed ``clear()`` to ``reset()``; added ``digest_size`` attribute
|
||||
to objects; added ``.hexdigest()`` method.
|
||||
|
||||
2001-09-20: Removed ``reset()`` method completely.
|
||||
|
||||
2001-09-28: Set ``digest_size`` to ``None`` for variable-size hashes.
|
||||
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
Thanks to Aahz, Andrew Archibald, Rich Salz, Itamar
|
||||
Shtull-Trauring, and the readers of the python-crypto list for
|
||||
their comments on this PEP.
|
||||
Thanks to Aahz, Andrew Archibald, Rich Salz, Itamar Shtull-Trauring, and the
|
||||
readers of the python-crypto list for their comments on this PEP.
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
This document has been placed in the public domain.
|
||||
|
||||
|
||||
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
End:
|
||||
|
||||
|
|
629
pep-0342.txt
629
pep-0342.txt
|
@ -5,161 +5,162 @@ Last-Modified: $Date$
|
|||
Author: Guido van Rossum, Phillip J. Eby
|
||||
Status: Final
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Content-Type: text/x-rst
|
||||
Created: 10-May-2005
|
||||
Python-Version: 2.5
|
||||
Post-History:
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
This PEP proposes some enhancements to the API and syntax of
|
||||
generators, to make them usable as simple coroutines. It is
|
||||
basically a combination of ideas from these two PEPs, which
|
||||
may be considered redundant if this PEP is accepted:
|
||||
This PEP proposes some enhancements to the API and syntax of generators, to
|
||||
make them usable as simple coroutines. It is basically a combination of ideas
|
||||
from these two PEPs, which may be considered redundant if this PEP is
|
||||
accepted:
|
||||
|
||||
- PEP 288, Generators Attributes and Exceptions. The current PEP
|
||||
covers its second half, generator exceptions (in fact the
|
||||
throw() method name was taken from PEP 288). PEP 342 replaces
|
||||
generator attributes, however, with a concept from an earlier
|
||||
revision of PEP 288, the "yield expression".
|
||||
- PEP 288, Generators Attributes and Exceptions. The current PEP covers its
|
||||
second half, generator exceptions (in fact the ``throw()`` method name was
|
||||
taken from PEP 288). PEP 342 replaces generator attributes, however, with a
|
||||
concept from an earlier revision of PEP 288, the *yield expression*.
|
||||
|
||||
- PEP 325, Resource-Release Support for Generators. PEP 342 ties up a few
|
||||
loose ends in the PEP 325 spec, to make it suitable for actual
|
||||
implementation.
|
||||
|
||||
- PEP 325, Resource-Release Support for Generators. PEP 342
|
||||
ties up a few loose ends in the PEP 325 spec, to make it suitable
|
||||
for actual implementation.
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
Coroutines are a natural way of expressing many algorithms, such as
|
||||
simulations, games, asynchronous I/O, and other forms of event-
|
||||
driven programming or co-operative multitasking. Python's generator
|
||||
functions are almost coroutines -- but not quite -- in that they
|
||||
allow pausing execution to produce a value, but do not provide for
|
||||
values or exceptions to be passed in when execution resumes. They
|
||||
also do not allow execution to be paused within the "try" portion of
|
||||
try/finally blocks, and therefore make it difficult for an aborted
|
||||
coroutine to clean up after itself.
|
||||
Coroutines are a natural way of expressing many algorithms, such as
|
||||
simulations, games, asynchronous I/O, and other forms of event-driven
|
||||
programming or co-operative multitasking. Python's generator functions are
|
||||
almost coroutines -- but not quite -- in that they allow pausing execution to
|
||||
produce a value, but do not provide for values or exceptions to be passed in
|
||||
when execution resumes. They also do not allow execution to be paused within
|
||||
the ``try`` portion of ``try/finally`` blocks, and therefore make it difficult
|
||||
for an aborted coroutine to clean up after itself.
|
||||
|
||||
Also, generators cannot yield control while other functions are
|
||||
executing, unless those functions are themselves expressed as
|
||||
generators, and the outer generator is written to yield in response
|
||||
to values yielded by the inner generator. This complicates the
|
||||
implementation of even relatively simple use cases like asynchronous
|
||||
communications, because calling any functions either requires the
|
||||
generator to "block" (i.e. be unable to yield control), or else a
|
||||
lot of boilerplate looping code must be added around every needed
|
||||
function call.
|
||||
Also, generators cannot yield control while other functions are executing,
|
||||
unless those functions are themselves expressed as generators, and the outer
|
||||
generator is written to yield in response to values yielded by the inner
|
||||
generator. This complicates the implementation of even relatively simple use
|
||||
cases like asynchronous communications, because calling any functions either
|
||||
requires the generator to *block* (i.e. be unable to yield control), or else a
|
||||
lot of boilerplate looping code must be added around every needed function
|
||||
call.
|
||||
|
||||
However, if it were possible to pass values or exceptions *into* a
|
||||
generator at the point where it was suspended, a simple co-routine
|
||||
scheduler or "trampoline function" would let coroutines "call" each
|
||||
other without blocking -- a tremendous boon for asynchronous
|
||||
applications. Such applications could then write co-routines to
|
||||
do non-blocking socket I/O by yielding control to an I/O scheduler
|
||||
until data has been sent or becomes available. Meanwhile, code that
|
||||
performs the I/O would simply do something like this:
|
||||
However, if it were possible to pass values or exceptions *into* a generator at
|
||||
the point where it was suspended, a simple co-routine scheduler or *trampoline
|
||||
function* would let coroutines *call* each other without blocking -- a
|
||||
tremendous boon for asynchronous applications. Such applications could then
|
||||
write co-routines to do non-blocking socket I/O by yielding control to an I/O
|
||||
scheduler until data has been sent or becomes available. Meanwhile, code that
|
||||
performs the I/O would simply do something like this::
|
||||
|
||||
data = (yield nonblocking_read(my_socket, nbytes))
|
||||
|
||||
in order to pause execution until the nonblocking_read() coroutine
|
||||
produced a value.
|
||||
in order to pause execution until the ``nonblocking_read()`` coroutine produced
|
||||
a value.
|
||||
|
||||
In other words, with a few relatively minor enhancements to the language and to
|
||||
the implementation of the generator-iterator type, Python will be able to
|
||||
support performing asynchronous operations without needing to write the entire
|
||||
application as a series of callbacks, and without requiring the use of
|
||||
resource-intensive threads for programs that need hundreds or even thousands of
|
||||
co-operatively multitasking pseudothreads. Thus, these enhancements will give
|
||||
standard Python many of the benefits of the Stackless Python fork, without
|
||||
requiring any significant modification to the CPython core or its APIs. In
|
||||
addition, these enhancements should be readily implementable by any Python
|
||||
implementation (such as Jython) that already supports generators.
|
||||
|
||||
In other words, with a few relatively minor enhancements to the
|
||||
language and to the implementation of the generator-iterator type,
|
||||
Python will be able to support performing asynchronous operations
|
||||
without needing to write the entire application as a series of
|
||||
callbacks, and without requiring the use of resource-intensive threads
|
||||
for programs that need hundreds or even thousands of co-operatively
|
||||
multitasking pseudothreads. Thus, these enhancements will give
|
||||
standard Python many of the benefits of the Stackless Python fork,
|
||||
without requiring any significant modification to the CPython core
|
||||
or its APIs. In addition, these enhancements should be readily
|
||||
implementable by any Python implementation (such as Jython) that
|
||||
already supports generators.
|
||||
|
||||
Specification Summary
|
||||
=====================
|
||||
|
||||
By adding a few simple methods to the generator-iterator type, and
|
||||
with two minor syntax adjustments, Python developers will be able
|
||||
to use generator functions to implement co-routines and other forms
|
||||
of co-operative multitasking. These methods and adjustments are:
|
||||
By adding a few simple methods to the generator-iterator type, and with two
|
||||
minor syntax adjustments, Python developers will be able to use generator
|
||||
functions to implement co-routines and other forms of co-operative
|
||||
multitasking. These methods and adjustments are:
|
||||
|
||||
1. Redefine "yield" to be an expression, rather than a statement.
|
||||
The current yield statement would become a yield expression
|
||||
whose value is thrown away. A yield expression's value is
|
||||
None whenever the generator is resumed by a normal next() call.
|
||||
1. Redefine ``yield`` to be an expression, rather than a statement. The current
|
||||
yield statement would become a yield expression whose value is thrown away.
|
||||
A yield expression's value is ``None`` whenever the generator is resumed by
|
||||
a normal ``next()`` call.
|
||||
|
||||
2. Add a new send() method for generator-iterators, which resumes
|
||||
the generator and "sends" a value that becomes the result of the
|
||||
current yield-expression. The send() method returns the next
|
||||
value yielded by the generator, or raises StopIteration if the
|
||||
generator exits without yielding another value.
|
||||
2. Add a new ``send()`` method for generator-iterators, which resumes the
|
||||
generator and *sends* a value that becomes the result of the current
|
||||
yield-expression. The ``send()`` method returns the next value yielded by
|
||||
the generator, or raises ``StopIteration`` if the generator exits without
|
||||
yielding another value.
|
||||
|
||||
3. Add a new throw() method for generator-iterators, which raises
|
||||
an exception at the point where the generator was paused, and
|
||||
which returns the next value yielded by the generator, raising
|
||||
StopIteration if the generator exits without yielding another
|
||||
value. (If the generator does not catch the passed-in exception,
|
||||
or raises a different exception, then that exception propagates
|
||||
to the caller.)
|
||||
3. Add a new ``throw()`` method for generator-iterators, which raises an
|
||||
exception at the point where the generator was paused, and which returns the
|
||||
next value yielded by the generator, raising ``StopIteration`` if the
|
||||
generator exits without yielding another value. (If the generator does not
|
||||
catch the passed-in exception, or raises a different exception, then that
|
||||
exception propagates to the caller.)
|
||||
|
||||
4. Add a close() method for generator-iterators, which raises
|
||||
GeneratorExit at the point where the generator was paused. If
|
||||
the generator then raises StopIteration (by exiting normally, or
|
||||
due to already being closed) or GeneratorExit (by not catching
|
||||
the exception), close() returns to its caller. If the generator
|
||||
yields a value, a RuntimeError is raised. If the generator
|
||||
raises any other exception, it is propagated to the caller.
|
||||
close() does nothing if the generator has already exited due to
|
||||
an exception or normal exit.
|
||||
4. Add a ``close()`` method for generator-iterators, which raises
|
||||
``GeneratorExit`` at the point where the generator was paused. If the
|
||||
generator then raises ``StopIteration`` (by exiting normally, or due to
|
||||
already being closed) or ``GeneratorExit`` (by not catching the exception),
|
||||
``close()`` returns to its caller. If the generator yields a value, a
|
||||
``RuntimeError`` is raised. If the generator raises any other exception, it
|
||||
is propagated to the caller. ``close()`` does nothing if the generator has
|
||||
already exited due to an exception or normal exit.
|
||||
|
||||
5. Add support to ensure that close() is called when a generator
|
||||
iterator is garbage-collected.
|
||||
5. Add support to ensure that ``close()`` is called when a generator iterator
|
||||
is garbage-collected.
|
||||
|
||||
6. Allow "yield" to be used in try/finally blocks, since garbage
|
||||
collection or an explicit close() call would now allow the
|
||||
finally clause to execute.
|
||||
6. Allow ``yield`` to be used in ``try/finally`` blocks, since garbage
|
||||
collection or an explicit ``close()`` call would now allow the ``finally``
|
||||
clause to execute.
|
||||
|
||||
A prototype patch implementing all of these changes against the
|
||||
current Python CVS HEAD is available as SourceForge patch #1223381
|
||||
(http://python.org/sf/1223381).
|
||||
A prototype patch implementing all of these changes against the current Python
|
||||
CVS HEAD is available as SourceForge patch #1223381
|
||||
(http://python.org/sf/1223381).
|
||||
|
||||
|
||||
Specification: Sending Values into Generators
|
||||
=============================================
|
||||
|
||||
New generator method: send(value)
|
||||
New generator method: ``send(value)``
|
||||
-------------------------------------
|
||||
|
||||
A new method for generator-iterators is proposed, called send(). It
|
||||
takes exactly one argument, which is the value that should be "sent
|
||||
in" to the generator. Calling send(None) is exactly equivalent to
|
||||
calling a generator's next() method. Calling send() with any other
|
||||
value is the same, except that the value produced by the generator's
|
||||
current yield expression will be different.
|
||||
A new method for generator-iterators is proposed, called ``send()``. It
|
||||
takes exactly one argument, which is the value that should be *sent in* to
|
||||
the generator. Calling ``send(None)`` is exactly equivalent to calling a
|
||||
generator's ``next()`` method. Calling ``send()`` with any other value is
|
||||
the same, except that the value produced by the generator's current
|
||||
yield expression will be different.
|
||||
|
||||
Because generator-iterators begin execution at the top of the
|
||||
generator's function body, there is no yield expression to receive
|
||||
a value when the generator has just been created. Therefore,
|
||||
calling send() with a non-None argument is prohibited when the
|
||||
generator iterator has just started, and a TypeError is raised if
|
||||
this occurs (presumably due to a logic error of some kind). Thus,
|
||||
before you can communicate with a coroutine you must first call
|
||||
next() or send(None) to advance its execution to the first yield
|
||||
expression.
|
||||
Because generator-iterators begin execution at the top of the generator's
|
||||
function body, there is no yield expression to receive a value when the
|
||||
generator has just been created. Therefore, calling ``send()`` with a
|
||||
non-``None`` argument is prohibited when the generator iterator has just
|
||||
started, and a ``TypeError`` is raised if this occurs (presumably due to a
|
||||
logic error of some kind). Thus, before you can communicate with a
|
||||
coroutine you must first call ``next()`` or ``send(None)`` to advance its
|
||||
execution to the first yield expression.
|
||||
|
||||
As with the next() method, the send() method returns the next value
|
||||
yielded by the generator-iterator, or raises StopIteration if the
|
||||
generator exits normally, or has already exited. If the generator
|
||||
raises an uncaught exception, it is propagated to send()'s caller.
|
||||
As with the ``next()`` method, the ``send()`` method returns the next value
|
||||
yielded by the generator-iterator, or raises ``StopIteration`` if the
|
||||
generator exits normally, or has already exited. If the generator raises an
|
||||
uncaught exception, it is propagated to ``send()``'s caller.
|
||||
|
||||
New syntax: Yield Expressions
|
||||
New syntax: Yield Expressions
|
||||
-----------------------------
|
||||
|
||||
The yield-statement will be allowed to be used on the right-hand
|
||||
side of an assignment; in that case it is referred to as
|
||||
yield-expression. The value of this yield-expression is None
|
||||
unless send() was called with a non-None argument; see below.
|
||||
The yield-statement will be allowed to be used on the right-hand side of an
|
||||
assignment; in that case it is referred to as yield-expression. The value
|
||||
of this yield-expression is ``None`` unless ``send()`` was called with a
|
||||
non-``None`` argument; see below.
|
||||
|
||||
A yield-expression must always be parenthesized except when it
|
||||
occurs at the top-level expression on the right-hand side of an
|
||||
assignment. So
|
||||
A yield-expression must always be parenthesized except when it occurs at the
|
||||
top-level expression on the right-hand side of an assignment. So
|
||||
|
||||
::
|
||||
|
||||
x = yield 42
|
||||
x = yield
|
||||
|
@ -168,107 +169,108 @@ Specification: Sending Values into Generators
|
|||
foo(yield 42)
|
||||
foo(yield)
|
||||
|
||||
are all legal, but
|
||||
are all legal, but
|
||||
|
||||
::
|
||||
|
||||
x = 12 + yield 42
|
||||
x = 12 + yield
|
||||
foo(yield 42, 12)
|
||||
foo(yield, 12)
|
||||
|
||||
are all illegal. (Some of the edge cases are motivated by the
|
||||
current legality of "yield 12, 42".)
|
||||
are all illegal. (Some of the edge cases are motivated by the current
|
||||
legality of ``yield 12, 42``.)
|
||||
|
||||
Note that a yield-statement or yield-expression without an
|
||||
expression is now legal. This makes sense: when the information
|
||||
flow in the next() call is reversed, it should be possible to
|
||||
yield without passing an explicit value ("yield" is of course
|
||||
equivalent to "yield None").
|
||||
Note that a yield-statement or yield-expression without an expression is now
|
||||
legal. This makes sense: when the information flow in the ``next()`` call
|
||||
is reversed, it should be possible to yield without passing an explicit
|
||||
value (``yield`` is of course equivalent to ``yield None``).
|
||||
|
||||
When send(value) is called, the yield-expression that it resumes
|
||||
will return the passed-in value. When next() is called, the resumed
|
||||
yield-expression will return None. If the yield-expression is a
|
||||
yield-statement, this returned value is ignored, similar to ignoring
|
||||
the value returned by a function call used as a statement.
|
||||
When ``send(value)`` is called, the yield-expression that it resumes will
|
||||
return the passed-in value. When ``next()`` is called, the resumed
|
||||
yield-expression will return ``None``. If the yield-expression is a
|
||||
yield-statement, this returned value is ignored, similar to ignoring the
|
||||
value returned by a function call used as a statement.
|
||||
|
||||
In effect, a yield-expression is like an inverted function call; the
|
||||
argument to yield is in fact returned (yielded) from the currently
|
||||
executing function, and the "return value" of yield is the argument
|
||||
passed in via send().
|
||||
In effect, a yield-expression is like an inverted function call; the
|
||||
argument to yield is in fact returned (yielded) from the currently executing
|
||||
function, and the *return value* of yield is the argument passed in via
|
||||
``send()``.
|
||||
|
||||
Note: the syntactic extensions to yield make its use very similar to that in
|
||||
Ruby. This is intentional. Do note that in Python the block passes a value
|
||||
to the generator using ``send(EXPR)`` rather than ``return EXPR``, and the
|
||||
underlying mechanism whereby control is passed between the generator and the
|
||||
block is completely different. Blocks in Python are not compiled into
|
||||
thunks; rather, ``yield`` suspends execution of the generator's frame. Some
|
||||
edge cases work differently; in Python, you cannot save the block for later
|
||||
use, and you cannot test whether there is a block or not. (XXX - this stuff
|
||||
about blocks seems out of place now, perhaps Guido can edit to clarify.)
|
||||
|
||||
Note: the syntactic extensions to yield make its use very similar
|
||||
to that in Ruby. This is intentional. Do note that in Python the
|
||||
block passes a value to the generator using "send(EXPR)" rather
|
||||
than "return EXPR", and the underlying mechanism whereby control
|
||||
is passed between the generator and the block is completely
|
||||
different. Blocks in Python are not compiled into thunks; rather,
|
||||
yield suspends execution of the generator's frame. Some edge
|
||||
cases work differently; in Python, you cannot save the block for
|
||||
later use, and you cannot test whether there is a block or not.
|
||||
(XXX - this stuff about blocks seems out of place now, perhaps
|
||||
Guido can edit to clarify.)
|
||||
|
||||
Specification: Exceptions and Cleanup
|
||||
=====================================
|
||||
|
||||
Let a generator object be the iterator produced by calling a
|
||||
generator function. Below, 'g' always refers to a generator
|
||||
object.
|
||||
Let a generator object be the iterator produced by calling a generator
|
||||
function. Below, *g* always refers to a generator object.
|
||||
|
||||
New syntax: yield allowed inside try-finally
|
||||
New syntax: ``yield`` allowed inside ``try-finally``
|
||||
----------------------------------------------------
|
||||
|
||||
The syntax for generator functions is extended to allow a
|
||||
yield-statement inside a try-finally statement.
|
||||
The syntax for generator functions is extended to allow a yield-statement
|
||||
inside a ``try-finally`` statement.
|
||||
|
||||
New generator method: throw(type, value=None, traceback=None)
|
||||
New generator method: ``throw(type, value=None, traceback=None)``
|
||||
-----------------------------------------------------------------
|
||||
|
||||
g.throw(type, value, traceback) causes the specified exception to
|
||||
be thrown at the point where the generator g is currently
|
||||
suspended (i.e. at a yield-statement, or at the start of its
|
||||
function body if next() has not been called yet). If the
|
||||
generator catches the exception and yields another value, that is
|
||||
the return value of g.throw(). If it doesn't catch the exception,
|
||||
the throw() appears to raise the same exception passed it (it
|
||||
"falls through"). If the generator raises another exception (this
|
||||
includes the StopIteration produced when it returns) that
|
||||
exception is raised by the throw() call. In summary, throw()
|
||||
behaves like next() or send(), except it raises an exception at the
|
||||
suspension point. If the generator is already in the closed
|
||||
state, throw() just raises the exception it was passed without
|
||||
executing any of the generator's code.
|
||||
``g.throw(type, value, traceback)`` causes the specified exception to be
|
||||
thrown at the point where the generator *g* is currently suspended (i.e. at
|
||||
a yield-statement, or at the start of its function body if ``next()`` has
|
||||
not been called yet). If the generator catches the exception and yields
|
||||
another value, that is the return value of ``g.throw()``. If it doesn't
|
||||
catch the exception, the ``throw()`` appears to raise the same exception
|
||||
passed it (it *falls through*). If the generator raises another exception
|
||||
(this includes the ``StopIteration`` produced when it returns) that
|
||||
exception is raised by the ``throw()`` call. In summary, ``throw()``
|
||||
behaves like ``next()`` or ``send()``, except it raises an exception at the
|
||||
suspension point. If the generator is already in the closed state,
|
||||
``throw()`` just raises the exception it was passed without executing any of
|
||||
the generator's code.
|
||||
|
||||
The effect of raising the exception is exactly as if the
|
||||
statement:
|
||||
The effect of raising the exception is exactly as if the statement::
|
||||
|
||||
raise type, value, traceback
|
||||
|
||||
was executed at the suspension point. The type argument must
|
||||
not be None, and the type and value must be compatible. If the
|
||||
value is not an instance of the type, a new exception instance
|
||||
is created using the value, following the same rules that the raise
|
||||
statement uses to create an exception instance. The traceback, if
|
||||
supplied, must be a valid Python traceback object, or a TypeError
|
||||
occurs.
|
||||
was executed at the suspension point. The type argument must not be
|
||||
``None``, and the type and value must be compatible. If the value is not an
|
||||
instance of the type, a new exception instance is created using the value,
|
||||
following the same rules that the ``raise`` statement uses to create an
|
||||
exception instance. The traceback, if supplied, must be a valid Python
|
||||
traceback object, or a ``TypeError`` occurs.
|
||||
|
||||
Note: The name of the throw() method was selected for several
|
||||
reasons. Raise is a keyword and so cannot be used as a method
|
||||
name. Unlike raise (which immediately raises an exception from the
|
||||
current execution point), throw() first resumes the generator, and
|
||||
only then raises the exception. The word throw is suggestive of
|
||||
putting the exception in another location, and is already associated
|
||||
with exceptions in other languages.
|
||||
Note: The name of the ``throw()`` method was selected for several reasons.
|
||||
``Raise`` is a keyword and so cannot be used as a method name. Unlike
|
||||
``raise`` (which immediately raises an exception from the current execution
|
||||
point), ``throw()`` first resumes the generator, and only then raises the
|
||||
exception. The word *throw* is suggestive of putting the exception in
|
||||
another location, and is already associated with exceptions in other
|
||||
languages.
|
||||
|
||||
Alternative method names were considered: resolve(), signal(),
|
||||
genraise(), raiseinto(), and flush(). None of these seem to fit
|
||||
as well as throw().
|
||||
Alternative method names were considered: ``resolve()``, ``signal()``,
|
||||
``genraise()``, ``raiseinto()``, and ``flush()``. None of these seem to fit
|
||||
as well as ``throw()``.
|
||||
|
||||
New standard exception: GeneratorExit
|
||||
New standard exception: ``GeneratorExit``
|
||||
-----------------------------------------
|
||||
|
||||
A new standard exception is defined, GeneratorExit, inheriting
|
||||
from Exception. A generator should handle this by re-raising it
|
||||
(or just not catching it) or by raising StopIteration.
|
||||
A new standard exception is defined, ``GeneratorExit``, inheriting from
|
||||
``Exception``. A generator should handle this by re-raising it (or just not
|
||||
catching it) or by raising ``StopIteration``.
|
||||
|
||||
New generator method: close()
|
||||
New generator method: ``close()``
|
||||
---------------------------------
|
||||
|
||||
g.close() is defined by the following pseudo-code:
|
||||
``g.close()`` is defined by the following pseudo-code::
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
|
@ -279,101 +281,101 @@ Specification: Exceptions and Cleanup
|
|||
raise RuntimeError("generator ignored GeneratorExit")
|
||||
# Other exceptions are not caught
|
||||
|
||||
New generator method: __del__()
|
||||
New generator method: __del__()
|
||||
-------------------------------
|
||||
|
||||
g.__del__() is a wrapper for g.close(). This will be called when
|
||||
the generator object is garbage-collected (in CPython, this is
|
||||
when its reference count goes to zero). If close() raises an
|
||||
exception, a traceback for the exception is printed to sys.stderr
|
||||
and further ignored; it is not propagated back to the place that
|
||||
triggered the garbage collection. This is consistent with the
|
||||
handling of exceptions in __del__() methods on class instances.
|
||||
``g.__del__()`` is a wrapper for ``g.close()``. This will be called when
|
||||
the generator object is garbage-collected (in CPython, this is when its
|
||||
reference count goes to zero). If ``close()`` raises an exception, a
|
||||
traceback for the exception is printed to ``sys.stderr`` and further
|
||||
ignored; it is not propagated back to the place that triggered the garbage
|
||||
collection. This is consistent with the handling of exceptions in
|
||||
``__del__()`` methods on class instances.
|
||||
|
||||
If the generator object participates in a cycle, g.__del__() may
|
||||
not be called. This is the behavior of CPython's current garbage
|
||||
collector. The reason for the restriction is that the GC code
|
||||
needs to "break" a cycle at an arbitrary point in order to collect
|
||||
it, and from then on no Python code should be allowed to see the
|
||||
objects that formed the cycle, as they may be in an invalid state.
|
||||
Objects "hanging off" a cycle are not subject to this restriction.
|
||||
If the generator object participates in a cycle, ``g.__del__()`` may not be
|
||||
called. This is the behavior of CPython's current garbage collector. The
|
||||
reason for the restriction is that the GC code needs to *break* a cycle at
|
||||
an arbitrary point in order to collect it, and from then on no Python code
|
||||
should be allowed to see the objects that formed the cycle, as they may be
|
||||
in an invalid state. Objects *hanging off* a cycle are not subject to this
|
||||
restriction.
|
||||
|
||||
Note that it is unlikely to see a generator object participate in
|
||||
a cycle in practice. However, storing a generator object in a
|
||||
global variable creates a cycle via the generator frame's
|
||||
f_globals pointer. Another way to create a cycle would be to
|
||||
store a reference to the generator object in a data structure that
|
||||
is passed to the generator as an argument (e.g., if an object has
|
||||
a method that's a generator, and keeps a reference to a running
|
||||
iterator created by that method). Neither of these cases
|
||||
are very likely given the typical patterns of generator use.
|
||||
Note that it is unlikely to see a generator object participate in a cycle in
|
||||
practice. However, storing a generator object in a global variable creates
|
||||
a cycle via the generator frame's ``f_globals`` pointer. Another way to
|
||||
create a cycle would be to store a reference to the generator object in a
|
||||
data structure that is passed to the generator as an argument (e.g., if an
|
||||
object has a method that's a generator, and keeps a reference to a running
|
||||
iterator created by that method). Neither of these cases are very likely
|
||||
given the typical patterns of generator use.
|
||||
|
||||
Also, in the CPython implementation of this PEP, the frame object used by
|
||||
the generator should be released whenever its execution is terminated due to
|
||||
an error or normal exit. This will ensure that generators that cannot be
|
||||
resumed do not remain part of an uncollectable reference cycle. This allows
|
||||
other code to potentially use ``close()`` in a ``try/finally`` or ``with``
|
||||
block (per PEP 343) to ensure that a given generator is properly finalized.
|
||||
|
||||
Also, in the CPython implementation of this PEP, the frame object
|
||||
used by the generator should be released whenever its execution is
|
||||
terminated due to an error or normal exit. This will ensure that
|
||||
generators that cannot be resumed do not remain part of an
|
||||
uncollectable reference cycle. This allows other code to
|
||||
potentially use close() in a try/finally or "with" block (per PEP
|
||||
343) to ensure that a given generator is properly finalized.
|
||||
|
||||
Optional Extensions
|
||||
===================
|
||||
|
||||
The Extended 'continue' Statement
|
||||
The Extended ``continue`` Statement
|
||||
-----------------------------------
|
||||
|
||||
An earlier draft of this PEP proposed a new ``continue EXPR`` syntax for use
|
||||
in for-loops (carried over from PEP 340), that would pass the value of
|
||||
*EXPR* into the iterator being looped over. This feature has been withdrawn
|
||||
for the time being, because the scope of this PEP has been narrowed to focus
|
||||
only on passing values into generator-iterators, and not other kinds of
|
||||
iterators. It was also felt by some on the Python-Dev list that adding new
|
||||
syntax for this particular feature would be premature at best.
|
||||
|
||||
An earlier draft of this PEP proposed a new "continue EXPR"
|
||||
syntax for use in for-loops (carried over from PEP 340), that
|
||||
would pass the value of EXPR into the iterator being looped over.
|
||||
This feature has been withdrawn for the time being, because the
|
||||
scope of this PEP has been narrowed to focus only on passing values
|
||||
into generator-iterators, and not other kinds of iterators. It
|
||||
was also felt by some on the Python-Dev list that adding new syntax
|
||||
for this particular feature would be premature at best.
|
||||
|
||||
Open Issues
|
||||
===========
|
||||
|
||||
Discussion on python-dev has revealed some open issues. I list
|
||||
them here, with my preferred resolution and its motivation. The
|
||||
PEP as currently written reflects this preferred resolution.
|
||||
Discussion on python-dev has revealed some open issues. I list them here, with
|
||||
my preferred resolution and its motivation. The PEP as currently written
|
||||
reflects this preferred resolution.
|
||||
|
||||
1. What exception should be raised by close() when the generator
|
||||
yields another value as a response to the GeneratorExit
|
||||
exception?
|
||||
1. What exception should be raised by ``close()`` when the generator yields
|
||||
another value as a response to the ``GeneratorExit`` exception?
|
||||
|
||||
I originally chose TypeError because it represents gross
|
||||
misbehavior of the generator function, which should be fixed by
|
||||
changing the code. But the with_template decorator class in
|
||||
PEP 343 uses RuntimeError for similar offenses. Arguably they
|
||||
should all use the same exception. I'd rather not introduce a
|
||||
new exception class just for this purpose, since it's not an
|
||||
exception that I want people to catch: I want it to turn into a
|
||||
traceback which is seen by the programmer who then fixes the
|
||||
code. So now I believe they should both raise RuntimeError.
|
||||
There are some precedents for that: it's raised by the core
|
||||
Python code in situations where endless recursion is detected,
|
||||
and for uninitialized objects (and for a variety of
|
||||
I originally chose ``TypeError`` because it represents gross misbehavior of
|
||||
the generator function, which should be fixed by changing the code. But the
|
||||
``with_template`` decorator class in PEP 343 uses ``RuntimeError`` for
|
||||
similar offenses. Arguably they should all use the same exception. I'd
|
||||
rather not introduce a new exception class just for this purpose, since it's
|
||||
not an exception that I want people to catch: I want it to turn into a
|
||||
traceback which is seen by the programmer who then fixes the code. So now I
|
||||
believe they should both raise ``RuntimeError``. There are some precedents
|
||||
for that: it's raised by the core Python code in situations where endless
|
||||
recursion is detected, and for uninitialized objects (and for a variety of
|
||||
miscellaneous conditions).
|
||||
|
||||
2. Oren Tirosh has proposed renaming the send() method to feed(),
|
||||
for compatibility with the "consumer interface" (see
|
||||
2. Oren Tirosh has proposed renaming the ``send()`` method to ``feed()``, for
|
||||
compatibility with the *consumer interface* (see
|
||||
http://effbot.org/zone/consumer.htm for the specification.)
|
||||
|
||||
However, looking more closely at the consumer interface, it seems
|
||||
that the desired semantics for feed() are different than for
|
||||
send(), because send() can't be meaningfully called on a just-
|
||||
started generator. Also, the consumer interface as currently
|
||||
defined doesn't include handling for StopIteration.
|
||||
However, looking more closely at the consumer interface, it seems that the
|
||||
desired semantics for ``feed()`` are different than for ``send()``, because
|
||||
``send()`` can't be meaningfully called on a just-started generator. Also,
|
||||
the consumer interface as currently defined doesn't include handling for
|
||||
``StopIteration``.
|
||||
|
||||
Therefore, it seems like it would probably be more useful to create a simple
|
||||
decorator that wraps a generator function to make it conform to the consumer
|
||||
interface. For example, it could *warm up* the generator with an initial
|
||||
``next()`` call, trap StopIteration, and perhaps even provide ``reset()`` by
|
||||
re-invoking the generator function.
|
||||
|
||||
Therefore, it seems like it would probably be more useful to
|
||||
create a simple decorator that wraps a generator function to make
|
||||
it conform to the consumer interface. For example, it could
|
||||
"warm up" the generator with an initial next() call, trap
|
||||
StopIteration, and perhaps even provide reset() by re-invoking
|
||||
the generator function.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
1. A simple "consumer" decorator that makes a generator function
|
||||
automatically advance to its first yield point when initially
|
||||
called:
|
||||
1. A simple *consumer* decorator that makes a generator function automatically
|
||||
advance to its first yield point when initially called::
|
||||
|
||||
def consumer(func):
|
||||
def wrapper(*args,**kw):
|
||||
|
@ -385,12 +387,11 @@ Examples
|
|||
wrapper.__doc__ = func.__doc__
|
||||
return wrapper
|
||||
|
||||
2. An example of using the "consumer" decorator to create a
|
||||
"reverse generator" that receives images and creates thumbnail
|
||||
pages, sending them on to another consumer. Functions like
|
||||
this can be chained together to form efficient processing
|
||||
pipelines of "consumers" that each can have complex internal
|
||||
state:
|
||||
2. An example of using the *consumer* decorator to create a *reverse generator*
|
||||
that receives images and creates thumbnail pages, sending them on to another
|
||||
consumer. Functions like this can be chained together to form efficient
|
||||
processing pipelines of *consumers* that each can have complex internal
|
||||
state::
|
||||
|
||||
@consumer
|
||||
def thumbnail_pager(pagesize, thumbsize, destination):
|
||||
|
@ -403,8 +404,7 @@ Examples
|
|||
for column in xrange(columns):
|
||||
thumb = create_thumbnail((yield), thumbsize)
|
||||
page.write(
|
||||
thumb, col*thumbsize.x, row*thumbsize.y
|
||||
)
|
||||
thumb, col*thumbsize.x, row*thumbsize.y )
|
||||
pending = True
|
||||
except GeneratorExit:
|
||||
# close() was called, so flush any pending output
|
||||
|
@ -420,7 +420,7 @@ Examples
|
|||
destination.send(page)
|
||||
|
||||
@consumer
|
||||
def jpeg_writer(dirname):
|
||||
def jpeg_writer(dirname)::
|
||||
fileno = 1
|
||||
while True:
|
||||
filename = os.path.join(dirname,"page%04d.jpg" % fileno)
|
||||
|
@ -441,22 +441,21 @@ Examples
|
|||
|
||||
pipeline.close()
|
||||
|
||||
3. A simple co-routine scheduler or "trampoline" that lets
|
||||
coroutines "call" other coroutines by yielding the coroutine
|
||||
they wish to invoke. Any non-generator value yielded by
|
||||
a coroutine is returned to the coroutine that "called" the
|
||||
one yielding the value. Similarly, if a coroutine raises an
|
||||
exception, the exception is propagated to its "caller". In
|
||||
effect, this example emulates simple tasklets as are used
|
||||
in Stackless Python, as long as you use a yield expression to
|
||||
invoke routines that would otherwise "block". This is only
|
||||
a very simple example, and far more sophisticated schedulers
|
||||
are possible. (For example, the existing GTasklet framework
|
||||
for Python (http://www.gnome.org/~gjc/gtasklet/gtasklets.html)
|
||||
and the peak.events framework (http://peak.telecommunity.com/)
|
||||
already implement similar scheduling capabilities, but must
|
||||
currently use awkward workarounds for the inability to pass
|
||||
values or exceptions into generators.)
|
||||
3. A simple co-routine scheduler or *trampoline* that lets coroutines *call*
|
||||
other coroutines by yielding the coroutine they wish to invoke. Any
|
||||
non-generator value yielded by a coroutine is returned to the coroutine that
|
||||
*called* the one yielding the value. Similarly, if a coroutine raises an
|
||||
exception, the exception is propagated to its *caller*. In effect, this
|
||||
example emulates simple tasklets as are used in Stackless Python, as long as
|
||||
you use a yield expression to invoke routines that would otherwise *block*.
|
||||
This is only a very simple example, and far more sophisticated schedulers
|
||||
are possible. (For example, the existing GTasklet framework for Python
|
||||
(http://www.gnome.org/~gjc/gtasklet/gtasklets.html) and the peak.events
|
||||
framework (http://peak.telecommunity.com/) already implement similar
|
||||
scheduling capabilities, but must currently use awkward workarounds for the
|
||||
inability to pass values or exceptions into generators.)
|
||||
|
||||
::
|
||||
|
||||
import collections
|
||||
|
||||
|
@ -476,7 +475,7 @@ Examples
|
|||
result = None
|
||||
self.running = True
|
||||
try:
|
||||
while self.running and self.queue:
|
||||
while self.running and self.queue::
|
||||
func = self.queue.popleft()
|
||||
result = func()
|
||||
return result
|
||||
|
@ -521,10 +520,10 @@ Examples
|
|||
|
||||
self.queue.append(resume)
|
||||
|
||||
4. A simple "echo" server, and code to run it using a trampoline
|
||||
(presumes the existence of "nonblocking_read",
|
||||
"nonblocking_write", and other I/O coroutines, that e.g. raise
|
||||
ConnectionLost if the connection is closed):
|
||||
4. A simple *echo* server, and code to run it using a trampoline (presumes the
|
||||
existence of ``nonblocking_read``, ``nonblocking_write``, and other I/O
|
||||
coroutines, that e.g. raise ``ConnectionLost`` if the connection is
|
||||
closed)::
|
||||
|
||||
# coroutine function that echos data back on a connected
|
||||
# socket
|
||||
|
@ -569,27 +568,41 @@ Examples
|
|||
|
||||
|
||||
Reference Implementation
|
||||
========================
|
||||
|
||||
A prototype patch implementing all of the features described in this
|
||||
PEP is available as SourceForge patch #1223381
|
||||
(http://python.org/sf/1223381).
|
||||
A prototype patch implementing all of the features described in this PEP is
|
||||
available as SourceForge patch #1223381 (http://python.org/sf/1223381).
|
||||
|
||||
This patch was committed to CVS 01-02 August 2005.
|
||||
This patch was committed to CVS 01-02 August 2005.
|
||||
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
Raymond Hettinger (PEP 288) and Samuele Pedroni (PEP 325) first formally
|
||||
proposed the ideas of communicating values or exceptions into generators, and
|
||||
the ability to *close* generators. Timothy Delaney suggested the title of this
|
||||
PEP, and Steven Bethard helped edit a previous version. See also the
|
||||
Acknowledgements section of PEP 340.
|
||||
|
||||
Raymond Hettinger (PEP 288) and Samuele Pedroni (PEP 325) first
|
||||
formally proposed the ideas of communicating values or exceptions
|
||||
into generators, and the ability to "close" generators. Timothy
|
||||
Delaney suggested the title of this PEP, and Steven Bethard helped
|
||||
edit a previous version. See also the Acknowledgements section
|
||||
of PEP 340.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
TBD.
|
||||
TBD.
|
||||
|
||||
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
|
||||
coding: utf-8
|
||||
End:
|
||||
|
||||
This document has been placed in the public domain.
|
||||
|
|
539
pep-0344.txt
539
pep-0344.txt
|
@ -5,144 +5,146 @@ Last-Modified: $Date$
|
|||
Author: Ka-Ping Yee
|
||||
Status: Superseded
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Content-Type: text/x-rst
|
||||
Created: 12-May-2005
|
||||
Python-Version: 2.5
|
||||
Post-History:
|
||||
|
||||
|
||||
Numbering Note
|
||||
==============
|
||||
|
||||
This PEP has been renumbered to PEP 3134. The text below is the
|
||||
last version submitted under the old number.
|
||||
This PEP has been renumbered to PEP 3134. The text below is the last version
|
||||
submitted under the old number.
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
This PEP proposes three standard attributes on exception instances:
|
||||
the '__context__' attribute for implicitly chained exceptions, the
|
||||
'__cause__' attribute for explicitly chained exceptions, and the
|
||||
'__traceback__' attribute for the traceback. A new "raise ... from"
|
||||
statement sets the '__cause__' attribute.
|
||||
This PEP proposes three standard attributes on exception instances: the
|
||||
``__context__`` attribute for implicitly chained exceptions, the
|
||||
``__cause__`` attribute for explicitly chained exceptions, and the
|
||||
``__traceback__`` attribute for the traceback. A new ``raise ... from``
|
||||
statement sets the ``__cause__`` attribute.
|
||||
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
During the handling of one exception (exception A), it is possible
|
||||
that another exception (exception B) may occur. In today's Python
|
||||
(version 2.4), if this happens, exception B is propagated outward
|
||||
and exception A is lost. In order to debug the problem, it is
|
||||
useful to know about both exceptions. The '__context__' attribute
|
||||
retains this information automatically.
|
||||
During the handling of one exception (exception A), it is possible that another
|
||||
exception (exception B) may occur. In today's Python (version 2.4), if this
|
||||
happens, exception B is propagated outward and exception A is lost. In order
|
||||
to debug the problem, it is useful to know about both exceptions. The
|
||||
``__context__`` attribute retains this information automatically.
|
||||
|
||||
Sometimes it can be useful for an exception handler to intentionally
|
||||
re-raise an exception, either to provide extra information or to
|
||||
translate an exception to another type. The '__cause__' attribute
|
||||
provides an explicit way to record the direct cause of an exception.
|
||||
Sometimes it can be useful for an exception handler to intentionally re-raise
|
||||
an exception, either to provide extra information or to translate an exception
|
||||
to another type. The ``__cause__`` attribute provides an explicit way to
|
||||
record the direct cause of an exception.
|
||||
|
||||
In today's Python implementation, exceptions are composed of three
|
||||
parts: the type, the value, and the traceback. The 'sys' module,
|
||||
exposes the current exception in three parallel variables, exc_type,
|
||||
exc_value, and exc_traceback, the sys.exc_info() function returns a
|
||||
tuple of these three parts, and the 'raise' statement has a
|
||||
three-argument form accepting these three parts. Manipulating
|
||||
exceptions often requires passing these three things in parallel,
|
||||
which can be tedious and error-prone. Additionally, the 'except'
|
||||
statement can only provide access to the value, not the traceback.
|
||||
Adding the '__traceback__' attribute to exception values makes all
|
||||
the exception information accessible from a single place.
|
||||
In today's Python implementation, exceptions are composed of three parts: the
|
||||
type, the value, and the traceback. The ``sys`` module, exposes the current
|
||||
exception in three parallel variables, ``exc_type``, ``exc_value``, and
|
||||
``exc_traceback``, the ``sys.exc_info()`` function returns a tuple of these
|
||||
three parts, and the ``raise`` statement has a three-argument form accepting
|
||||
these three parts. Manipulating exceptions often requires passing these three
|
||||
things in parallel, which can be tedious and error-prone. Additionally, the
|
||||
``except`` statement can only provide access to the value, not the traceback.
|
||||
Adding the ``__traceback__`` attribute to exception values makes all the
|
||||
exception information accessible from a single place.
|
||||
|
||||
|
||||
History
|
||||
=======
|
||||
|
||||
Raymond Hettinger [1] raised the issue of masked exceptions on
|
||||
Python-Dev in January 2003 and proposed a PyErr_FormatAppend()
|
||||
function that C modules could use to augment the currently active
|
||||
exception with more information. Brett Cannon [2] brought up
|
||||
chained exceptions again in June 2003, prompting a long discussion.
|
||||
Raymond Hettinger [1]_ raised the issue of masked exceptions on Python-Dev in
|
||||
January 2003 and proposed a ``PyErr_FormatAppend()`` function that C modules
|
||||
could use to augment the currently active exception with more information.
|
||||
Brett Cannon [2]_ brought up chained exceptions again in June 2003, prompting
|
||||
a long discussion.
|
||||
|
||||
Greg Ewing [3] identified the case of an exception occurring in a
|
||||
'finally' block during unwinding triggered by an original exception,
|
||||
as distinct from the case of an exception occurring in an 'except'
|
||||
block that is handling the original exception.
|
||||
Greg Ewing [3]_ identified the case of an exception occurring in a ``finally``
|
||||
block during unwinding triggered by an original exception, as distinct from
|
||||
the case of an exception occurring in an ``except`` block that is handling the
|
||||
original exception.
|
||||
|
||||
Greg Ewing [4] and Guido van Rossum [5], and probably others, have
|
||||
previously mentioned adding a traceback attribute to Exception
|
||||
instances. This is noted in PEP 3000.
|
||||
Greg Ewing [4]_ and Guido van Rossum [5]_, and probably others, have
|
||||
previously mentioned adding a traceback attribute to ``Exception`` instances.
|
||||
This is noted in PEP 3000.
|
||||
|
||||
This PEP was motivated by yet another recent Python-Dev reposting
|
||||
of the same ideas [6] [7].
|
||||
This PEP was motivated by yet another recent Python-Dev reposting of the same
|
||||
ideas [6]_ [7]_.
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
The Python-Dev discussions revealed interest in exception chaining
|
||||
for two quite different purposes. To handle the unexpected raising
|
||||
of a secondary exception, the exception must be retained implicitly.
|
||||
To support intentional translation of an exception, there must be a
|
||||
way to chain exceptions explicitly. This PEP addresses both.
|
||||
The Python-Dev discussions revealed interest in exception chaining for two
|
||||
quite different purposes. To handle the unexpected raising of a secondary
|
||||
exception, the exception must be retained implicitly. To support intentional
|
||||
translation of an exception, there must be a way to chain exceptions
|
||||
explicitly. This PEP addresses both.
|
||||
|
||||
Several attribute names for chained exceptions have been suggested
|
||||
on Python-Dev [2], including 'cause', 'antecedent', 'reason',
|
||||
'original', 'chain', 'chainedexc', 'exc_chain', 'excprev',
|
||||
'previous', and 'precursor'. For an explicitly chained exception,
|
||||
this PEP suggests '__cause__' because of its specific meaning. For
|
||||
an implicitly chained exception, this PEP proposes the name
|
||||
'__context__' because the intended meaning is more specific than
|
||||
temporal precedence but less specific than causation: an exception
|
||||
occurs in the context of handling another exception.
|
||||
Several attribute names for chained exceptions have been suggested on Python-
|
||||
Dev [2]_, including ``cause``, ``antecedent``, ``reason``, ``original``,
|
||||
``chain``, ``chainedexc``, ``xc_chain``, ``excprev``, ``previous`` and
|
||||
``precursor``. For an explicitly chained exception, this PEP suggests
|
||||
``__cause__`` because of its specific meaning. For an implicitly chained
|
||||
exception, this PEP proposes the name ``__context__`` because the intended
|
||||
meaning is more specific than temporal precedence but less specific than
|
||||
causation: an exception occurs in the context of handling another exception.
|
||||
|
||||
This PEP suggests names with leading and trailing double-underscores
|
||||
for these three attributes because they are set by the Python VM.
|
||||
Only in very special cases should they be set by normal assignment.
|
||||
This PEP suggests names with leading and trailing double-underscores for these
|
||||
three attributes because they are set by the Python VM. Only in very special
|
||||
cases should they be set by normal assignment.
|
||||
|
||||
This PEP handles exceptions that occur during 'except' blocks and
|
||||
'finally' blocks in the same way. Reading the traceback makes it
|
||||
clear where the exceptions occurred, so additional mechanisms for
|
||||
distinguishing the two cases would only add unnecessary complexity.
|
||||
This PEP handles exceptions that occur during ``except`` blocks and
|
||||
``finally`` blocks in the same way. Reading the traceback makes it clear
|
||||
where the exceptions occurred, so additional mechanisms for distinguishing
|
||||
the two cases would only add unnecessary complexity.
|
||||
|
||||
This PEP proposes that the outermost exception object (the one
|
||||
exposed for matching by 'except' clauses) be the most recently
|
||||
raised exception for compatibility with current behaviour.
|
||||
This PEP proposes that the outermost exception object (the one exposed for
|
||||
matching by ``except`` clauses) be the most recently raised exception for
|
||||
compatibility with current behaviour.
|
||||
|
||||
This PEP proposes that tracebacks display the outermost exception
|
||||
last, because this would be consistent with the chronological order
|
||||
of tracebacks (from oldest to most recent frame) and because the
|
||||
actual thrown exception is easier to find on the last line.
|
||||
This PEP proposes that tracebacks display the outermost exception last,
|
||||
because this would be consistent with the chronological order of tracebacks
|
||||
(from oldest to most recent frame) and because the actual thrown exception is
|
||||
easier to find on the last line.
|
||||
|
||||
To keep things simpler, the C API calls for setting an exception
|
||||
will not automatically set the exception's '__context__'. Guido
|
||||
van Rossum has expressed concerns with making such changes [8].
|
||||
To keep things simpler, the C API calls for setting an exception will not
|
||||
automatically set the exception's ``__context__``. Guido van Rossum has
|
||||
expressed concerns with making such changes [8]_.
|
||||
|
||||
As for other languages, Java and Ruby both discard the original
|
||||
exception when another exception occurs in a 'catch'/'rescue' or
|
||||
'finally'/'ensure' clause. Perl 5 lacks built-in structured
|
||||
exception handling. For Perl 6, RFC number 88 [9] proposes an exception
|
||||
mechanism that implicitly retains chained exceptions in an array
|
||||
named @@. In that RFC, the most recently raised exception is
|
||||
exposed for matching, as in this PEP; also, arbitrary expressions
|
||||
(possibly involving @@) can be evaluated for exception matching.
|
||||
As for other languages, Java and Ruby both discard the original exception when
|
||||
another exception occurs in a ``catch/rescue`` or ``finally/ensure`` clause.
|
||||
Perl 5 lacks built-in structured exception handling. For Perl 6, RFC number
|
||||
88 [9]_ proposes an exception mechanism that implicitly retains chained
|
||||
exceptions in an array named ``@@``. In that RFC, the most recently raised
|
||||
exception is exposed for matching, as in this PEP; also, arbitrary expressions
|
||||
(possibly involving ``@@``) can be evaluated for exception matching.
|
||||
|
||||
Exceptions in C# contain a read-only 'InnerException' property that
|
||||
may point to another exception. Its documentation [10] says that
|
||||
"When an exception X is thrown as a direct result of a previous
|
||||
exception Y, the InnerException property of X should contain a
|
||||
reference to Y." This property is not set by the VM automatically;
|
||||
rather, all exception constructors take an optional 'innerException'
|
||||
argument to set it explicitly. The '__cause__' attribute fulfills
|
||||
the same purpose as InnerException, but this PEP proposes a new form
|
||||
of 'raise' rather than extending the constructors of all exceptions.
|
||||
C# also provides a GetBaseException method that jumps directly to
|
||||
the end of the InnerException chain; this PEP proposes no analog.
|
||||
Exceptions in C# contain a read-only ``InnerException`` property that may
|
||||
point to another exception. Its documentation [10]_ says that "When an
|
||||
exception X is thrown as a direct result of a previous exception Y, the
|
||||
``InnerException`` property of X should contain a reference to Y." This
|
||||
property is not set by the VM automatically; rather, all exception
|
||||
constructors take an optional ``innerException`` argument to set it
|
||||
explicitly. The ``__cause__`` attribute fulfills the same purpose as
|
||||
``InnerException``, but this PEP proposes a new form of ``raise`` rather than
|
||||
extending the constructors of all exceptions. C# also provides a
|
||||
``GetBaseException`` method that jumps directly to the end of the
|
||||
``InnerException`` chain; this PEP proposes no analog.
|
||||
|
||||
The reason all three of these attributes are presented together in
|
||||
one proposal is that the '__traceback__' attribute provides
|
||||
convenient access to the traceback on chained exceptions.
|
||||
The reason all three of these attributes are presented together in one proposal
|
||||
is that the ``__traceback__`` attribute provides convenient access to the
|
||||
traceback on chained exceptions.
|
||||
|
||||
|
||||
Implicit Exception Chaining
|
||||
===========================
|
||||
|
||||
Here is an example to illustrate the '__context__' attribute.
|
||||
Here is an example to illustrate the ``__context__`` attribute::
|
||||
|
||||
def compute(a, b):
|
||||
try:
|
||||
|
@ -155,18 +157,18 @@ Implicit Exception Chaining
|
|||
print >>file, exc
|
||||
file.close()
|
||||
|
||||
Calling compute(0, 0) causes a ZeroDivisionError. The compute()
|
||||
function catches this exception and calls log(exc), but the log()
|
||||
function also raises an exception when it tries to write to a
|
||||
file that wasn't opened for writing.
|
||||
Calling ``compute(0, 0)`` causes a ``ZeroDivisionError``. The ``compute()``
|
||||
function catches this exception and calls ``log(exc)``, but the ``log()``
|
||||
function also raises an exception when it tries to write to a file that wasn't
|
||||
opened for writing.
|
||||
|
||||
In today's Python, the caller of compute() gets thrown an IOError.
|
||||
The ZeroDivisionError is lost. With the proposed change, the
|
||||
instance of IOError has an additional '__context__' attribute that
|
||||
retains the ZeroDivisionError.
|
||||
In today's Python, the caller of ``compute()`` gets thrown an ``IOError``. The
|
||||
``ZeroDivisionError`` is lost. With the proposed change, the instance of
|
||||
``IOError`` has an additional ``__context__`` attribute that retains the
|
||||
``ZeroDivisionError``.
|
||||
|
||||
The following more elaborate example demonstrates the handling of a
|
||||
mixture of 'finally' and 'except' clauses:
|
||||
The following more elaborate example demonstrates the handling of a mixture of
|
||||
``finally`` and ``except`` clauses::
|
||||
|
||||
def main(filename):
|
||||
file = open(filename) # oops, forgot the 'w'
|
||||
|
@ -190,47 +192,48 @@ Implicit Exception Chaining
|
|||
def display(exc):
|
||||
print ex # oops, misspelled 'exc'
|
||||
|
||||
Calling main() with the name of an existing file will trigger four
|
||||
exceptions. The ultimate result will be an AttributeError due to
|
||||
the misspelling of 'clos', whose __context__ points to a NameError
|
||||
due to the misspelling of 'ex', whose __context__ points to an
|
||||
IOError due to the file being read-only, whose __context__ points to
|
||||
a ZeroDivisionError, whose __context__ attribute is None.
|
||||
Calling ``main()`` with the name of an existing file will trigger four
|
||||
exceptions. The ultimate result will be an ``AttributeError`` due to the
|
||||
misspelling of ``clos``, whose ``__context__`` points to a ``NameError`` due
|
||||
to the misspelling of ``ex``, whose ``__context__`` points to an ``IOError``
|
||||
due to the file being read-only, whose ``__context__`` points to a
|
||||
``ZeroDivisionError``, whose ``__context__`` attribute is ``None``.
|
||||
|
||||
The proposed semantics are as follows:
|
||||
The proposed semantics are as follows:
|
||||
|
||||
1. Each thread has an exception context initially set to None.
|
||||
1. Each thread has an exception context initially set to ``None``.
|
||||
|
||||
2. Whenever an exception is raised, if the exception instance does
|
||||
not already have a '__context__' attribute, the interpreter sets
|
||||
it equal to the thread's exception context.
|
||||
2. Whenever an exception is raised, if the exception instance does not
|
||||
already have a ``__context__`` attribute, the interpreter sets it equal to
|
||||
the thread's exception context.
|
||||
|
||||
3. Immediately after an exception is raised, the thread's exception
|
||||
context is set to the exception.
|
||||
3. Immediately after an exception is raised, the thread's exception context is
|
||||
set to the exception.
|
||||
|
||||
4. Whenever the interpreter exits an 'except' block by reaching the
|
||||
end or executing a 'return', 'yield', 'continue', or 'break'
|
||||
statement, the thread's exception context is set to None.
|
||||
4. Whenever the interpreter exits an ``except`` block by reaching the end or
|
||||
executing a ``return``, ``yield``, ``continue``, or ``break`` statement,
|
||||
the thread's exception context is set to ``None``.
|
||||
|
||||
|
||||
Explicit Exception Chaining
|
||||
===========================
|
||||
|
||||
The '__cause__' attribute on exception objects is always initialized
|
||||
to None. It is set by a new form of the 'raise' statement:
|
||||
The ``__cause__`` attribute on exception objects is always initialized to
|
||||
``None``. It is set by a new form of the ``raise`` statement::
|
||||
|
||||
raise EXCEPTION from CAUSE
|
||||
|
||||
which is equivalent to:
|
||||
which is equivalent to::
|
||||
|
||||
exc = EXCEPTION
|
||||
exc.__cause__ = CAUSE
|
||||
raise exc
|
||||
|
||||
In the following example, a database provides implementations for a
|
||||
few different kinds of storage, with file storage as one kind. The
|
||||
database designer wants errors to propagate as DatabaseError objects
|
||||
so that the client doesn't have to be aware of the storage-specific
|
||||
details, but doesn't want to lose the underlying error information.
|
||||
In the following example, a database provides implementations for a few
|
||||
different kinds of storage, with file storage as one kind. The database
|
||||
designer wants errors to propagate as ``DatabaseError`` objects so that the
|
||||
client doesn't have to be aware of the storage-specific details, but doesn't
|
||||
want to lose the underlying error information::
|
||||
|
||||
class DatabaseError(StandardError):
|
||||
pass
|
||||
|
@ -242,14 +245,15 @@ Explicit Exception Chaining
|
|||
except IOError, exc:
|
||||
raise DatabaseError('failed to open') from exc
|
||||
|
||||
If the call to open() raises an exception, the problem will be
|
||||
reported as a DatabaseError, with a __cause__ attribute that reveals
|
||||
the IOError as the original cause.
|
||||
If the call to ``open()`` raises an exception, the problem will be reported as
|
||||
a ``DatabaseError``, with a ``__cause__`` attribute that reveals the
|
||||
``IOError`` as the original cause.
|
||||
|
||||
|
||||
Traceback Attribute
|
||||
===================
|
||||
|
||||
The following example illustrates the '__traceback__' attribute.
|
||||
The following example illustrates the ``__traceback__`` attribute::
|
||||
|
||||
def do_logged(file, work):
|
||||
try:
|
||||
|
@ -268,38 +272,40 @@ Traceback Attribute
|
|||
file.write(... type ... message ... lines ...)
|
||||
...
|
||||
|
||||
In today's Python, the do_logged() function would have to extract
|
||||
the traceback from sys.exc_traceback or sys.exc_info()[2] and pass
|
||||
both the value and the traceback to write_exception(). With the
|
||||
proposed change, write_exception() simply gets one argument and
|
||||
obtains the exception using the '__traceback__' attribute.
|
||||
In today's Python, the ``do_logged()`` function would have to extract the
|
||||
traceback from ``sys.exc_traceback`` or ``sys.exc_info()`` [2]_ and pass both
|
||||
the value and the traceback to ``write_exception()``. With the proposed
|
||||
change, ``write_exception()`` simply gets one argument and obtains the
|
||||
exception using the ``__traceback__`` attribute.
|
||||
|
||||
The proposed semantics are as follows:
|
||||
The proposed semantics are as follows:
|
||||
|
||||
1. Whenever an exception is caught, if the exception instance does
|
||||
not already have a '__traceback__' attribute, the interpreter
|
||||
sets it to the newly caught traceback.
|
||||
1. Whenever an exception is caught, if the exception instance does not already
|
||||
have a ``__traceback__`` attribute, the interpreter sets it to the newly
|
||||
caught traceback.
|
||||
|
||||
|
||||
Enhanced Reporting
|
||||
==================
|
||||
|
||||
The default exception handler will be modified to report chained
|
||||
exceptions. The chain of exceptions is traversed by following the
|
||||
'__cause__' and '__context__' attributes, with '__cause__' taking
|
||||
priority. In keeping with the chronological order of tracebacks,
|
||||
the most recently raised exception is displayed last; that is, the
|
||||
display begins with the description of the innermost exception and
|
||||
backs up the chain to the outermost exception. The tracebacks are
|
||||
formatted as usual, with one of the lines:
|
||||
The default exception handler will be modified to report chained exceptions.
|
||||
The chain of exceptions is traversed by following the ``__cause__`` and
|
||||
``__context__`` attributes, with ``__cause__`` taking priority. In keeping
|
||||
with the chronological order of tracebacks, the most recently raised exception
|
||||
is displayed last; that is, the display begins with the description of the
|
||||
innermost exception and backs up the chain to the outermost exception. The
|
||||
tracebacks are formatted as usual, with one of the lines::
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
::
|
||||
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
between tracebacks, depending whether they are linked by __cause__
|
||||
or __context__ respectively. Here is a sketch of the procedure:
|
||||
between tracebacks, depending whether they are linked by ``__cause__`` or
|
||||
``__context__`` respectively. Here is a sketch of the procedure::
|
||||
|
||||
def print_chain(exc):
|
||||
if exc.__cause__:
|
||||
|
@ -310,64 +316,68 @@ Enhanced Reporting
|
|||
print '\nDuring handling of the above exception, ...'
|
||||
print_exc(exc)
|
||||
|
||||
In the 'traceback' module, the format_exception, print_exception,
|
||||
print_exc, and print_last functions will be updated to accept an
|
||||
optional 'chain' argument, True by default. When this argument is
|
||||
True, these functions will format or display the entire chain of
|
||||
exceptions as just described. When it is False, these functions
|
||||
will format or display only the outermost exception.
|
||||
In the ``traceback`` module, the ``format_exception``, ``print_exception``,
|
||||
``print_exc``, and ``print_last functions`` will be updated to accept an
|
||||
optional ``chain`` argument, ``True`` by default. When this argument is
|
||||
``True``, these functions will format or display the entire chain of
|
||||
exceptions as just described. When it is ``False``, these functions will
|
||||
format or display only the outermost exception.
|
||||
|
||||
The 'cgitb' module should also be updated to display the entire
|
||||
chain of exceptions.
|
||||
The ``cgitb`` module should also be updated to display the entire chain of
|
||||
exceptions.
|
||||
|
||||
|
||||
C API
|
||||
=====
|
||||
|
||||
The PyErr_Set* calls for setting exceptions will not set the
|
||||
'__context__' attribute on exceptions. PyErr_NormalizeException
|
||||
will always set the 'traceback' attribute to its 'tb' argument and
|
||||
the '__context__' and '__cause__' attributes to None.
|
||||
The ``PyErr_Set*`` calls for setting exceptions will not set the
|
||||
``__context__`` attribute on exceptions. ``PyErr_NormalizeException`` will
|
||||
always set the ``traceback`` attribute to its ``tb`` argument and the
|
||||
``__context__`` and ``__cause__`` attributes to ``None``.
|
||||
|
||||
A new API function, PyErr_SetContext(context), will help C
|
||||
programmers provide chained exception information. This function
|
||||
will first normalize the current exception so it is an instance,
|
||||
then set its '__context__' attribute. A similar API function,
|
||||
PyErr_SetCause(cause), will set the '__cause__' attribute.
|
||||
A new API function, ``PyErr_SetContext(context)``, will help C programmers
|
||||
provide chained exception information. This function will first normalize the
|
||||
current exception so it is an instance, then set its ``__context__``
|
||||
attribute. A similar API function, ``PyErr_SetCause(cause)``, will set the
|
||||
``__cause__`` attribute.
|
||||
|
||||
|
||||
Compatibility
|
||||
=============
|
||||
|
||||
Chained exceptions expose the type of the most recent exception, so
|
||||
they will still match the same 'except' clauses as they do now.
|
||||
Chained exceptions expose the type of the most recent exception, so they will
|
||||
still match the same ``except`` clauses as they do now.
|
||||
|
||||
The proposed changes should not break any code unless it sets or
|
||||
uses attributes named '__context__', '__cause__', or '__traceback__'
|
||||
on exception instances. As of 2005-05-12, the Python standard
|
||||
library contains no mention of such attributes.
|
||||
The proposed changes should not break any code unless it sets or uses
|
||||
attributes named ``__context__``, ``__cause__``, or ``__traceback__`` on
|
||||
exception instances. As of 2005-05-12, the Python standard library contains
|
||||
no mention of such attributes.
|
||||
|
||||
|
||||
Open Issue: Extra Information
|
||||
==============================
|
||||
|
||||
Walter Dörwald [11] expressed a desire to attach extra information
|
||||
to an exception during its upward propagation without changing its
|
||||
type. This could be a useful feature, but it is not addressed by
|
||||
this PEP. It could conceivably be addressed by a separate PEP
|
||||
establishing conventions for other informational attributes on
|
||||
exceptions.
|
||||
Walter Dörwald [11]_ expressed a desire to attach extra information to an
|
||||
exception during its upward propagation without changing its type. This could
|
||||
be a useful feature, but it is not addressed by this PEP. It could
|
||||
conceivably be addressed by a separate PEP establishing conventions for other
|
||||
informational attributes on exceptions.
|
||||
|
||||
|
||||
Open Issue: Suppressing Context
|
||||
================================
|
||||
|
||||
As written, this PEP makes it impossible to suppress '__context__',
|
||||
since setting exc.__context__ to None in an 'except' or 'finally'
|
||||
clause will only result in it being set again when exc is raised.
|
||||
As written, this PEP makes it impossible to suppress ``__context__``, since
|
||||
setting ``exc.__context__`` to ``None`` in an ``except`` or ``finally`` clause
|
||||
will only result in it being set again when ``exc`` is raised.
|
||||
|
||||
|
||||
Open Issue: Limiting Exception Types
|
||||
=====================================
|
||||
|
||||
To improve encapsulation, library implementors may want to wrap all
|
||||
implementation-level exceptions with an application-level exception.
|
||||
One could try to wrap exceptions by writing this:
|
||||
To improve encapsulation, library implementors may want to wrap all
|
||||
implementation-level exceptions with an application-level exception. One could
|
||||
try to wrap exceptions by writing this::
|
||||
|
||||
try:
|
||||
... implementation may raise an exception ...
|
||||
|
@ -375,16 +385,18 @@ Open Issue: Limiting Exception Types
|
|||
import sys
|
||||
raise ApplicationError from sys.exc_value
|
||||
|
||||
or this:
|
||||
or this
|
||||
|
||||
::
|
||||
|
||||
try:
|
||||
... implementation may raise an exception ...
|
||||
except Exception, exc:
|
||||
raise ApplicationError from exc
|
||||
|
||||
but both are somewhat flawed. It would be nice to be able to name
|
||||
the current exception in a catch-all 'except' clause, but that isn't
|
||||
addressed here. Such a feature would allow something like this:
|
||||
but both are somewhat flawed. It would be nice to be able to name the current
|
||||
exception in a catch-all ``except`` clause, but that isn't addressed here.
|
||||
Such a feature would allow something like this::
|
||||
|
||||
try:
|
||||
... implementation may raise an exception ...
|
||||
|
@ -393,11 +405,12 @@ Open Issue: Limiting Exception Types
|
|||
|
||||
|
||||
Open Issue: yield
|
||||
==================
|
||||
|
||||
The exception context is lost when a 'yield' statement is executed;
|
||||
resuming the frame after the 'yield' does not restore the context.
|
||||
Addressing this problem is out of the scope of this PEP; it is not a
|
||||
new problem, as demonstrated by the following example:
|
||||
The exception context is lost when a ``yield`` statement is executed; resuming
|
||||
the frame after the ``yield`` does not restore the context. Addressing this
|
||||
problem is out of the scope of this PEP; it is not a new problem, as
|
||||
demonstrated by the following example::
|
||||
|
||||
>>> def gen():
|
||||
... try:
|
||||
|
@ -415,131 +428,137 @@ Open Issue: yield
|
|||
|
||||
|
||||
Open Issue: Garbage Collection
|
||||
===============================
|
||||
|
||||
The strongest objection to this proposal has been that it creates
|
||||
cycles between exceptions and stack frames [12]. Collection of
|
||||
cyclic garbage (and therefore resource release) can be greatly
|
||||
delayed.
|
||||
The strongest objection to this proposal has been that it creates cycles
|
||||
between exceptions and stack frames [12]_. Collection of cyclic garbage (and
|
||||
therefore resource release) can be greatly delayed::
|
||||
|
||||
>>> try:
|
||||
>>> 1/0
|
||||
>>> except Exception, err:
|
||||
>>> pass
|
||||
|
||||
will introduce a cycle from err -> traceback -> stack frame -> err,
|
||||
keeping all locals in the same scope alive until the next GC happens.
|
||||
will introduce a cycle from err -> traceback -> stack frame -> err, keeping
|
||||
all locals in the same scope alive until the next GC happens.
|
||||
|
||||
Today, these locals would go out of scope. There is lots of code
|
||||
which assumes that "local" resources -- particularly open files -- will
|
||||
be closed quickly. If closure has to wait for the next GC, a program
|
||||
(which runs fine today) may run out of file handles.
|
||||
Today, these locals would go out of scope. There is lots of code which
|
||||
assumes that "local" resources -- particularly open files -- will be closed
|
||||
quickly. If closure has to wait for the next GC, a program (which runs fine
|
||||
today) may run out of file handles.
|
||||
|
||||
Making the __traceback__ attribute a weak reference would avoid the
|
||||
problems with cyclic garbage. Unfortunately, it would make saving
|
||||
the Exception for later (as unittest does) more awkward, and it would
|
||||
not allow as much cleanup of the sys module.
|
||||
Making the ``__traceback__`` attribute a weak reference would avoid the
|
||||
problems with cyclic garbage. Unfortunately, it would make saving the
|
||||
``Exception`` for later (as ``unittest`` does) more awkward, and it would not
|
||||
allow as much cleanup of the ``sys`` module.
|
||||
|
||||
A possible alternate solution, suggested by Adam Olsen, would be to
|
||||
instead turn the reference from the stack frame to the 'err' variable
|
||||
into a weak reference when the variable goes out of scope [13].
|
||||
A possible alternate solution, suggested by Adam Olsen, would be to instead
|
||||
turn the reference from the stack frame to the ``err`` variable into a weak
|
||||
reference when the variable goes out of scope [13]_.
|
||||
|
||||
|
||||
Possible Future Compatible Changes
|
||||
==================================
|
||||
|
||||
These changes are consistent with the appearance of exceptions as
|
||||
a single object rather than a triple at the interpreter level.
|
||||
These changes are consistent with the appearance of exceptions as a single
|
||||
object rather than a triple at the interpreter level.
|
||||
|
||||
- If PEP 340 or PEP 343 is accepted, replace the three (type, value,
|
||||
traceback) arguments to __exit__ with a single exception argument.
|
||||
- If PEP 340 or PEP 343 is accepted, replace the three (``type``, ``value``,
|
||||
``traceback``) arguments to ``__exit__`` with a single exception argument.
|
||||
|
||||
- Deprecate sys.exc_type, sys.exc_value, sys.exc_traceback, and
|
||||
sys.exc_info() in favour of a single member, sys.exception.
|
||||
- Deprecate ``sys.exc_type``, ``sys.exc_value``, ``sys.exc_traceback``, and
|
||||
``sys.exc_info()`` in favour of a single member, ``sys.exception``.
|
||||
|
||||
- Deprecate sys.last_type, sys.last_value, and sys.last_traceback
|
||||
in favour of a single member, sys.last_exception.
|
||||
- Deprecate ``sys.last_type``, ``sys.last_value``, and ``sys.last_traceback``
|
||||
in favour of a single member, ``sys.last_exception``.
|
||||
|
||||
- Deprecate the three-argument form of the 'raise' statement in
|
||||
favour of the one-argument form.
|
||||
- Deprecate the three-argument form of the ``raise`` statement in favour of
|
||||
the one-argument form.
|
||||
|
||||
- Upgrade cgitb.html() to accept a single value as its first
|
||||
argument as an alternative to a (type, value, traceback) tuple.
|
||||
- Upgrade ``cgitb.html()`` to accept a single value as its first argument as
|
||||
an alternative to a ``(type, value, traceback)`` tuple.
|
||||
|
||||
|
||||
Possible Future Incompatible Changes
|
||||
====================================
|
||||
|
||||
These changes might be worth considering for Python 3000.
|
||||
These changes might be worth considering for Python 3000.
|
||||
|
||||
- Remove sys.exc_type, sys.exc_value, sys.exc_traceback, and
|
||||
sys.exc_info().
|
||||
- Remove ``sys.exc_type``, ``sys.exc_value``, ``sys.exc_traceback``, and
|
||||
``sys.exc_info()``.
|
||||
|
||||
- Remove sys.last_type, sys.last_value, and sys.last_traceback.
|
||||
- Remove ``sys.last_type``, ``sys.last_value``, and ``sys.last_traceback``.
|
||||
|
||||
- Replace the three-argument sys.excepthook with a one-argument
|
||||
API, and changing the 'cgitb' module to match.
|
||||
- Replace the three-argument ``sys.excepthook`` with a one-argument API, and
|
||||
changing the ``cgitb`` module to match.
|
||||
|
||||
- Remove the three-argument form of the 'raise' statement.
|
||||
- Remove the three-argument form of the ``raise`` statement.
|
||||
|
||||
- Upgrade traceback.print_exception to accept an 'exception'
|
||||
argument instead of the type, value, and traceback arguments.
|
||||
- Upgrade ``traceback.print_exception`` to accept an ``exception`` argument
|
||||
instead of the ``type``, ``value``, and ``traceback`` arguments.
|
||||
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
Brett Cannon, Greg Ewing, Guido van Rossum, Jeremy Hylton, Phillip
|
||||
J. Eby, Raymond Hettinger, Walter Dörwald, and others.
|
||||
Brett Cannon, Greg Ewing, Guido van Rossum, Jeremy Hylton, Phillip J. Eby,
|
||||
Raymond Hettinger, Walter Dörwald, and others.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
[1] Raymond Hettinger, "Idea for avoiding exception masking"
|
||||
.. [1] Raymond Hettinger, "Idea for avoiding exception masking"
|
||||
http://mail.python.org/pipermail/python-dev/2003-January/032492.html
|
||||
|
||||
[2] Brett Cannon explains chained exceptions
|
||||
.. [2] Brett Cannon explains chained exceptions
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036063.html
|
||||
|
||||
[3] Greg Ewing points out masking caused by exceptions during finally
|
||||
.. [3] Greg Ewing points out masking caused by exceptions during finally
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036290.html
|
||||
|
||||
[4] Greg Ewing suggests storing the traceback in the exception object
|
||||
.. [4] Greg Ewing suggests storing the traceback in the exception object
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036092.html
|
||||
|
||||
[5] Guido van Rossum mentions exceptions having a traceback attribute
|
||||
.. [5] Guido van Rossum mentions exceptions having a traceback attribute
|
||||
http://mail.python.org/pipermail/python-dev/2005-April/053060.html
|
||||
|
||||
[6] Ka-Ping Yee, "Tidier Exceptions"
|
||||
.. [6] Ka-Ping Yee, "Tidier Exceptions"
|
||||
http://mail.python.org/pipermail/python-dev/2005-May/053671.html
|
||||
|
||||
[7] Ka-Ping Yee, "Chained Exceptions"
|
||||
.. [7] Ka-Ping Yee, "Chained Exceptions"
|
||||
http://mail.python.org/pipermail/python-dev/2005-May/053672.html
|
||||
|
||||
[8] Guido van Rossum discusses automatic chaining in PyErr_Set*
|
||||
.. [8] Guido van Rossum discusses automatic chaining in ``PyErr_Set*``
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036180.html
|
||||
|
||||
[9] Tony Olensky, "Omnibus Structured Exception/Error Handling Mechanism"
|
||||
.. [9] Tony Olensky, "Omnibus Structured Exception/Error Handling Mechanism"
|
||||
http://dev.perl.org/perl6/rfc/88.html
|
||||
|
||||
[10] MSDN .NET Framework Library, "Exception.InnerException Property"
|
||||
.. [10] MSDN .NET Framework Library, "Exception.InnerException Property"
|
||||
http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemexceptionclassinnerexceptiontopic.asp
|
||||
|
||||
[11] Walter Dörwald suggests wrapping exceptions to add details
|
||||
.. [11] Walter Dörwald suggests wrapping exceptions to add details
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036148.html
|
||||
|
||||
[12] Guido van Rossum restates the objection to cyclic trash
|
||||
.. [12] Guido van Rossum restates the objection to cyclic trash
|
||||
http://mail.python.org/pipermail/python-3000/2007-January/005322.html
|
||||
|
||||
[13] Adam Olsen suggests using a weakref from stack frame to exception
|
||||
.. [13] Adam Olsen suggests using a weakref from stack frame to exception
|
||||
http://mail.python.org/pipermail/python-3000/2007-January/005363.html
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
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
|
||||
coding: utf-8
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 70
|
||||
coding: utf-8
|
||||
End:
|
||||
|
|
550
pep-3134.txt
550
pep-3134.txt
|
@ -5,144 +5,145 @@ Last-Modified: $Date$
|
|||
Author: Ka-Ping Yee
|
||||
Status: Final
|
||||
Type: Standards Track
|
||||
Content-Type: text/plain
|
||||
Content-Type: text/x-rst
|
||||
Created: 12-May-2005
|
||||
Python-Version: 3.0
|
||||
Post-History:
|
||||
|
||||
|
||||
Numbering Note
|
||||
==============
|
||||
|
||||
This PEP started its life as PEP 344. Since it is now targeted
|
||||
for Python 3000, it has been moved into the 3xxx space.
|
||||
This PEP started its life as PEP 344. Since it is now targeted for Python
|
||||
3000, it has been moved into the 3xxx space.
|
||||
|
||||
|
||||
Abstract
|
||||
========
|
||||
|
||||
This PEP proposes three standard attributes on exception instances:
|
||||
the '__context__' attribute for implicitly chained exceptions, the
|
||||
'__cause__' attribute for explicitly chained exceptions, and the
|
||||
'__traceback__' attribute for the traceback. A new "raise ... from"
|
||||
statement sets the '__cause__' attribute.
|
||||
This PEP proposes three standard attributes on exception instances: the
|
||||
``__context__`` attribute for implicitly chained exceptions, the ``__cause__``
|
||||
attribute for explicitly chained exceptions, and the ``__traceback__``
|
||||
attribute for the traceback. A new ``raise ... from`` statement sets the
|
||||
``__cause__`` attribute.
|
||||
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
During the handling of one exception (exception A), it is possible
|
||||
that another exception (exception B) may occur. In today's Python
|
||||
(version 2.4), if this happens, exception B is propagated outward
|
||||
and exception A is lost. In order to debug the problem, it is
|
||||
useful to know about both exceptions. The '__context__' attribute
|
||||
retains this information automatically.
|
||||
During the handling of one exception (exception A), it is possible that another
|
||||
exception (exception B) may occur. In today's Python (version 2.4), if this
|
||||
happens, exception B is propagated outward and exception A is lost. In order
|
||||
to debug the problem, it is useful to know about both exceptions. The
|
||||
``__context__`` attribute retains this information automatically.
|
||||
|
||||
Sometimes it can be useful for an exception handler to intentionally
|
||||
re-raise an exception, either to provide extra information or to
|
||||
translate an exception to another type. The '__cause__' attribute
|
||||
provides an explicit way to record the direct cause of an exception.
|
||||
Sometimes it can be useful for an exception handler to intentionally re-raise
|
||||
an exception, either to provide extra information or to translate an exception
|
||||
to another type. The ``__cause__`` attribute provides an explicit way to
|
||||
record the direct cause of an exception.
|
||||
|
||||
In today's Python implementation, exceptions are composed of three
|
||||
parts: the type, the value, and the traceback. The 'sys' module,
|
||||
exposes the current exception in three parallel variables, exc_type,
|
||||
exc_value, and exc_traceback, the sys.exc_info() function returns a
|
||||
tuple of these three parts, and the 'raise' statement has a
|
||||
three-argument form accepting these three parts. Manipulating
|
||||
exceptions often requires passing these three things in parallel,
|
||||
which can be tedious and error-prone. Additionally, the 'except'
|
||||
statement can only provide access to the value, not the traceback.
|
||||
Adding the '__traceback__' attribute to exception values makes all
|
||||
the exception information accessible from a single place.
|
||||
In today's Python implementation, exceptions are composed of three parts: the
|
||||
type, the value, and the traceback. The ``sys`` module, exposes the current
|
||||
exception in three parallel variables, ``exc_type``, ``exc_value``, and
|
||||
``exc_traceback``, the ``sys.exc_info()`` function returns a tuple of these
|
||||
three parts, and the ``raise`` statement has a three-argument form accepting
|
||||
these three parts. Manipulating exceptions often requires passing these three
|
||||
things in parallel, which can be tedious and error-prone. Additionally, the
|
||||
``except`` statement can only provide access to the value, not the traceback.
|
||||
Adding the ``__traceback__`` attribute to exception values makes all the
|
||||
exception information accessible from a single place.
|
||||
|
||||
|
||||
History
|
||||
=======
|
||||
|
||||
Raymond Hettinger [1] raised the issue of masked exceptions on
|
||||
Python-Dev in January 2003 and proposed a PyErr_FormatAppend()
|
||||
function that C modules could use to augment the currently active
|
||||
exception with more information. Brett Cannon [2] brought up
|
||||
chained exceptions again in June 2003, prompting a long discussion.
|
||||
Raymond Hettinger [1]_ raised the issue of masked exceptions on Python-Dev in
|
||||
January 2003 and proposed a ``PyErr_FormatAppend()`` function that C modules
|
||||
could use to augment the currently active exception with more information.
|
||||
Brett Cannon [2]_ brought up chained exceptions again in June 2003, prompting
|
||||
a long discussion.
|
||||
|
||||
Greg Ewing [3] identified the case of an exception occurring in a
|
||||
'finally' block during unwinding triggered by an original exception,
|
||||
as distinct from the case of an exception occurring in an 'except'
|
||||
block that is handling the original exception.
|
||||
Greg Ewing [3]_ identified the case of an exception occurring in a ``finally``
|
||||
block during unwinding triggered by an original exception, as distinct from
|
||||
the case of an exception occurring in an ``except`` block that is handling the
|
||||
original exception.
|
||||
|
||||
Greg Ewing [4] and Guido van Rossum [5], and probably others, have
|
||||
previously mentioned adding a traceback attribute to Exception
|
||||
instances. This is noted in PEP 3000.
|
||||
Greg Ewing [4]_ and Guido van Rossum [5]_, and probably others, have
|
||||
previously mentioned adding a traceback attribute to Exception instances.
|
||||
This is noted in PEP 3000.
|
||||
|
||||
This PEP was motivated by yet another recent Python-Dev reposting
|
||||
of the same ideas [6] [7].
|
||||
This PEP was motivated by yet another recent Python-Dev reposting of the same
|
||||
ideas [6]_ [7]_.
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
The Python-Dev discussions revealed interest in exception chaining
|
||||
for two quite different purposes. To handle the unexpected raising
|
||||
of a secondary exception, the exception must be retained implicitly.
|
||||
To support intentional translation of an exception, there must be a
|
||||
way to chain exceptions explicitly. This PEP addresses both.
|
||||
The Python-Dev discussions revealed interest in exception chaining for two
|
||||
quite different purposes. To handle the unexpected raising of a secondary
|
||||
exception, the exception must be retained implicitly. To support intentional
|
||||
translation of an exception, there must be a way to chain exceptions
|
||||
explicitly. This PEP addresses both.
|
||||
|
||||
Several attribute names for chained exceptions have been suggested
|
||||
on Python-Dev [2], including 'cause', 'antecedent', 'reason',
|
||||
'original', 'chain', 'chainedexc', 'exc_chain', 'excprev',
|
||||
'previous', and 'precursor'. For an explicitly chained exception,
|
||||
this PEP suggests '__cause__' because of its specific meaning. For
|
||||
an implicitly chained exception, this PEP proposes the name
|
||||
'__context__' because the intended meaning is more specific than
|
||||
temporal precedence but less specific than causation: an exception
|
||||
occurs in the context of handling another exception.
|
||||
Several attribute names for chained exceptions have been suggested on
|
||||
Python-Dev [2]_, including ``cause``, ``antecedent``, ``reason``, ``original``,
|
||||
``chain``, ``chainedexc``, ``exc_chain``, ``excprev``, ``previous``, and
|
||||
``precursor``. For an explicitly chained exception, this PEP suggests
|
||||
``__cause__`` because of its specific meaning. For an implicitly chained
|
||||
exception, this PEP proposes the name ``__context__`` because the intended
|
||||
meaning is more specific than temporal precedence but less specific than
|
||||
causation: an exception occurs in the context of handling another exception.
|
||||
|
||||
This PEP suggests names with leading and trailing double-underscores
|
||||
for these three attributes because they are set by the Python VM.
|
||||
Only in very special cases should they be set by normal assignment.
|
||||
This PEP suggests names with leading and trailing double-underscores for these
|
||||
three attributes because they are set by the Python VM. Only in very special
|
||||
cases should they be set by normal assignment.
|
||||
|
||||
This PEP handles exceptions that occur during 'except' blocks and
|
||||
'finally' blocks in the same way. Reading the traceback makes it
|
||||
clear where the exceptions occurred, so additional mechanisms for
|
||||
distinguishing the two cases would only add unnecessary complexity.
|
||||
This PEP handles exceptions that occur during ``except`` blocks and ``finally``
|
||||
blocks in the same way. Reading the traceback makes it clear where the
|
||||
exceptions occurred, so additional mechanisms for distinguishing the two cases
|
||||
would only add unnecessary complexity.
|
||||
|
||||
This PEP proposes that the outermost exception object (the one
|
||||
exposed for matching by 'except' clauses) be the most recently
|
||||
raised exception for compatibility with current behaviour.
|
||||
This PEP proposes that the outermost exception object (the one exposed for
|
||||
matching by ``except`` clauses) be the most recently raised exception for
|
||||
compatibility with current behaviour.
|
||||
|
||||
This PEP proposes that tracebacks display the outermost exception
|
||||
last, because this would be consistent with the chronological order
|
||||
of tracebacks (from oldest to most recent frame) and because the
|
||||
actual thrown exception is easier to find on the last line.
|
||||
This PEP proposes that tracebacks display the outermost exception last, because
|
||||
this would be consistent with the chronological order of tracebacks (from
|
||||
oldest to most recent frame) and because the actual thrown exception is easier
|
||||
to find on the last line.
|
||||
|
||||
To keep things simpler, the C API calls for setting an exception
|
||||
will not automatically set the exception's '__context__'. Guido
|
||||
van Rossum has expressed concerns with making such changes [8].
|
||||
To keep things simpler, the C API calls for setting an exception will not
|
||||
automatically set the exception's ``__context__``. Guido van Rossum has
|
||||
expressed concerns with making such changes [8]_.
|
||||
|
||||
As for other languages, Java and Ruby both discard the original
|
||||
exception when another exception occurs in a 'catch'/'rescue' or
|
||||
'finally'/'ensure' clause. Perl 5 lacks built-in structured
|
||||
exception handling. For Perl 6, RFC number 88 [9] proposes an exception
|
||||
mechanism that implicitly retains chained exceptions in an array
|
||||
named @@. In that RFC, the most recently raised exception is
|
||||
exposed for matching, as in this PEP; also, arbitrary expressions
|
||||
(possibly involving @@) can be evaluated for exception matching.
|
||||
As for other languages, Java and Ruby both discard the original exception when
|
||||
another exception occurs in a ``catch``/``rescue`` or ``finally``/``ensure``
|
||||
clause. Perl 5 lacks built-in structured exception handling. For Perl 6, RFC
|
||||
number 88 [9]_ proposes an exception mechanism that implicitly retains chained
|
||||
exceptions in an array named ``@@``. In that RFC, the most recently raised
|
||||
exception is exposed for matching, as in this PEP; also, arbitrary expressions
|
||||
(possibly involving ``@@``) can be evaluated for exception matching.
|
||||
|
||||
Exceptions in C# contain a read-only 'InnerException' property that
|
||||
may point to another exception. Its documentation [10] says that
|
||||
"When an exception X is thrown as a direct result of a previous
|
||||
exception Y, the InnerException property of X should contain a
|
||||
reference to Y." This property is not set by the VM automatically;
|
||||
rather, all exception constructors take an optional 'innerException'
|
||||
argument to set it explicitly. The '__cause__' attribute fulfills
|
||||
the same purpose as InnerException, but this PEP proposes a new form
|
||||
of 'raise' rather than extending the constructors of all exceptions.
|
||||
C# also provides a GetBaseException method that jumps directly to
|
||||
the end of the InnerException chain; this PEP proposes no analog.
|
||||
Exceptions in C# contain a read-only ``InnerException`` property that may point
|
||||
to another exception. Its documentation [10]_ says that "When an exception X
|
||||
is thrown as a direct result of a previous exception Y, the ``InnerException``
|
||||
property of X should contain a reference to Y." This property is not set by
|
||||
the VM automatically; rather, all exception constructors take an optional
|
||||
``innerException`` argument to set it explicitly. The ``__cause__`` attribute
|
||||
fulfills the same purpose as ``InnerException``, but this PEP proposes a new
|
||||
form of ``raise`` rather than extending the constructors of all exceptions. C#
|
||||
also provides a ``GetBaseException`` method that jumps directly to the end of
|
||||
the ``InnerException`` chain; this PEP proposes no analog.
|
||||
|
||||
The reason all three of these attributes are presented together in
|
||||
one proposal is that the '__traceback__' attribute provides
|
||||
convenient access to the traceback on chained exceptions.
|
||||
The reason all three of these attributes are presented together in one proposal
|
||||
is that the ``__traceback__`` attribute provides convenient access to the
|
||||
traceback on chained exceptions.
|
||||
|
||||
|
||||
Implicit Exception Chaining
|
||||
===========================
|
||||
|
||||
Here is an example to illustrate the '__context__' attribute.
|
||||
Here is an example to illustrate the ``__context__`` attribute::
|
||||
|
||||
def compute(a, b):
|
||||
try:
|
||||
|
@ -155,18 +156,18 @@ Implicit Exception Chaining
|
|||
print >>file, exc
|
||||
file.close()
|
||||
|
||||
Calling compute(0, 0) causes a ZeroDivisionError. The compute()
|
||||
function catches this exception and calls log(exc), but the log()
|
||||
function also raises an exception when it tries to write to a
|
||||
file that wasn't opened for writing.
|
||||
Calling ``compute(0, 0)`` causes a ``ZeroDivisionError``. The ``compute()``
|
||||
function catches this exception and calls ``log(exc)``, but the ``log()``
|
||||
function also raises an exception when it tries to write to a file that wasn't
|
||||
opened for writing.
|
||||
|
||||
In today's Python, the caller of compute() gets thrown an IOError.
|
||||
The ZeroDivisionError is lost. With the proposed change, the
|
||||
instance of IOError has an additional '__context__' attribute that
|
||||
retains the ZeroDivisionError.
|
||||
In today's Python, the caller of ``compute()`` gets thrown an ``IOError``. The
|
||||
``ZeroDivisionError`` is lost. With the proposed change, the instance of
|
||||
``IOError`` has an additional ``__context__`` attribute that retains the
|
||||
``ZeroDivisionError``.
|
||||
|
||||
The following more elaborate example demonstrates the handling of a
|
||||
mixture of 'finally' and 'except' clauses:
|
||||
The following more elaborate example demonstrates the handling of a mixture of
|
||||
``finally`` and ``except`` clauses::
|
||||
|
||||
def main(filename):
|
||||
file = open(filename) # oops, forgot the 'w'
|
||||
|
@ -190,47 +191,50 @@ Implicit Exception Chaining
|
|||
def display(exc):
|
||||
print ex # oops, misspelled 'exc'
|
||||
|
||||
Calling main() with the name of an existing file will trigger four
|
||||
exceptions. The ultimate result will be an AttributeError due to
|
||||
the misspelling of 'clos', whose __context__ points to a NameError
|
||||
due to the misspelling of 'ex', whose __context__ points to an
|
||||
IOError due to the file being read-only, whose __context__ points to
|
||||
a ZeroDivisionError, whose __context__ attribute is None.
|
||||
Calling ``main()`` with the name of an existing file will trigger four
|
||||
exceptions. The ultimate result will be an ``AttributeError`` due to the
|
||||
misspelling of ``clos``, whose ``__context__`` points to a ``NameError`` due
|
||||
to the misspelling of ``ex``, whose ``__context__`` points to an ``IOError``
|
||||
due to the file being read-only, whose ``__context__`` points to a
|
||||
``ZeroDivisionError``, whose ``__context__`` attribute is ``None``.
|
||||
|
||||
The proposed semantics are as follows:
|
||||
The proposed semantics are as follows:
|
||||
|
||||
1. Each thread has an exception context initially set to None.
|
||||
1. Each thread has an exception context initially set to ``None``.
|
||||
|
||||
2. Whenever an exception is raised, if the exception instance does
|
||||
not already have a '__context__' attribute, the interpreter sets
|
||||
it equal to the thread's exception context.
|
||||
2. Whenever an exception is raised, if the exception instance does not already
|
||||
have a ``__context__`` attribute, the interpreter sets it equal to the
|
||||
thread's exception context.
|
||||
|
||||
3. Immediately after an exception is raised, the thread's exception
|
||||
context is set to the exception.
|
||||
3. Immediately after an exception is raised, the thread's exception context is
|
||||
set to the exception.
|
||||
|
||||
4. Whenever the interpreter exits an 'except' block by reaching the
|
||||
end or executing a 'return', 'yield', 'continue', or 'break'
|
||||
statement, the thread's exception context is set to None.
|
||||
4. Whenever the interpreter exits an ``except`` block by reaching the end or
|
||||
executing a ``return``, ``yield``, ``continue``, or ``break`` statement, the
|
||||
thread's exception context is set to ``None``.
|
||||
|
||||
|
||||
Explicit Exception Chaining
|
||||
===========================
|
||||
|
||||
The '__cause__' attribute on exception objects is always initialized
|
||||
to None. It is set by a new form of the 'raise' statement:
|
||||
The ``__cause__`` attribute on exception objects is always initialized to
|
||||
``None``. It is set by a new form of the ``raise`` statement::
|
||||
|
||||
raise EXCEPTION from CAUSE
|
||||
|
||||
which is equivalent to:
|
||||
which is equivalent to::
|
||||
|
||||
exc = EXCEPTION
|
||||
exc.__cause__ = CAUSE
|
||||
raise exc
|
||||
|
||||
In the following example, a database provides implementations for a
|
||||
few different kinds of storage, with file storage as one kind. The
|
||||
database designer wants errors to propagate as DatabaseError objects
|
||||
so that the client doesn't have to be aware of the storage-specific
|
||||
details, but doesn't want to lose the underlying error information.
|
||||
In the following example, a database provides implementations for a few
|
||||
different kinds of storage, with file storage as one kind. The database
|
||||
designer wants errors to propagate as ``DatabaseError`` objects so that the
|
||||
client doesn't have to be aware of the storage-specific details, but doesn't
|
||||
want to lose the underlying error information.
|
||||
|
||||
::
|
||||
|
||||
class DatabaseError(Exception):
|
||||
pass
|
||||
|
@ -242,14 +246,17 @@ Explicit Exception Chaining
|
|||
except IOError, exc:
|
||||
raise DatabaseError('failed to open') from exc
|
||||
|
||||
If the call to open() raises an exception, the problem will be
|
||||
reported as a DatabaseError, with a __cause__ attribute that reveals
|
||||
the IOError as the original cause.
|
||||
If the call to ``open()`` raises an exception, the problem will be reported as
|
||||
a ``DatabaseError``, with a ``__cause__`` attribute that reveals the
|
||||
``IOError`` as the original cause.
|
||||
|
||||
|
||||
Traceback Attribute
|
||||
===================
|
||||
|
||||
The following example illustrates the '__traceback__' attribute.
|
||||
The following example illustrates the ``__traceback__`` attribute.
|
||||
|
||||
::
|
||||
|
||||
def do_logged(file, work):
|
||||
try:
|
||||
|
@ -268,38 +275,40 @@ Traceback Attribute
|
|||
file.write(... type ... message ... lines ...)
|
||||
...
|
||||
|
||||
In today's Python, the do_logged() function would have to extract
|
||||
the traceback from sys.exc_traceback or sys.exc_info()[2] and pass
|
||||
both the value and the traceback to write_exception(). With the
|
||||
proposed change, write_exception() simply gets one argument and
|
||||
obtains the exception using the '__traceback__' attribute.
|
||||
In today's Python, the ``do_logged()`` function would have to extract the
|
||||
traceback from ``sys.exc_traceback`` or ``sys.exc_info()`` [2]_ and pass both
|
||||
the value and the traceback to ``write_exception()``. With the proposed
|
||||
change, ``write_exception()`` simply gets one argument and obtains the
|
||||
exception using the ``__traceback__`` attribute.
|
||||
|
||||
The proposed semantics are as follows:
|
||||
The proposed semantics are as follows:
|
||||
|
||||
1. Whenever an exception is caught, if the exception instance does
|
||||
not already have a '__traceback__' attribute, the interpreter
|
||||
sets it to the newly caught traceback.
|
||||
1. Whenever an exception is caught, if the exception instance does not already
|
||||
have a ``__traceback__`` attribute, the interpreter sets it to the newly
|
||||
caught traceback.
|
||||
|
||||
|
||||
Enhanced Reporting
|
||||
==================
|
||||
|
||||
The default exception handler will be modified to report chained
|
||||
exceptions. The chain of exceptions is traversed by following the
|
||||
'__cause__' and '__context__' attributes, with '__cause__' taking
|
||||
priority. In keeping with the chronological order of tracebacks,
|
||||
the most recently raised exception is displayed last; that is, the
|
||||
display begins with the description of the innermost exception and
|
||||
backs up the chain to the outermost exception. The tracebacks are
|
||||
formatted as usual, with one of the lines:
|
||||
The default exception handler will be modified to report chained exceptions.
|
||||
The chain of exceptions is traversed by following the ``__cause__`` and
|
||||
``__context__`` attributes, with ``__cause__`` taking priority. In keeping
|
||||
with the chronological order of tracebacks, the most recently raised exception
|
||||
is displayed last; that is, the display begins with the description of the
|
||||
innermost exception and backs up the chain to the outermost exception. The
|
||||
tracebacks are formatted as usual, with one of the lines::
|
||||
|
||||
The above exception was the direct cause of the following exception:
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
::
|
||||
|
||||
During handling of the above exception, another exception occurred:
|
||||
|
||||
between tracebacks, depending whether they are linked by __cause__
|
||||
or __context__ respectively. Here is a sketch of the procedure:
|
||||
between tracebacks, depending whether they are linked by ``__cause__`` or
|
||||
``__context__`` respectively. Here is a sketch of the procedure::
|
||||
|
||||
def print_chain(exc):
|
||||
if exc.__cause__:
|
||||
|
@ -310,64 +319,68 @@ Enhanced Reporting
|
|||
print '\nDuring handling of the above exception, ...'
|
||||
print_exc(exc)
|
||||
|
||||
In the 'traceback' module, the format_exception, print_exception,
|
||||
print_exc, and print_last functions will be updated to accept an
|
||||
optional 'chain' argument, True by default. When this argument is
|
||||
True, these functions will format or display the entire chain of
|
||||
exceptions as just described. When it is False, these functions
|
||||
will format or display only the outermost exception.
|
||||
In the ``traceback`` module, the ``format_exception``, ``print_exception``,
|
||||
``print_exc``, and ``print_last`` functions will be updated to accept an
|
||||
optional ``chain`` argument, ``True`` by default. When this argument is
|
||||
``True``, these functions will format or display the entire chain of exceptions
|
||||
as just described. When it is ``False``, these functions will format or
|
||||
display only the outermost exception.
|
||||
|
||||
The 'cgitb' module should also be updated to display the entire
|
||||
chain of exceptions.
|
||||
The ``cgitb`` module should also be updated to display the entire chain of
|
||||
exceptions.
|
||||
|
||||
|
||||
C API
|
||||
=====
|
||||
|
||||
The PyErr_Set* calls for setting exceptions will not set the
|
||||
'__context__' attribute on exceptions. PyErr_NormalizeException
|
||||
will always set the 'traceback' attribute to its 'tb' argument and
|
||||
the '__context__' and '__cause__' attributes to None.
|
||||
The ``PyErr_Set*`` calls for setting exceptions will not set the
|
||||
``__context__`` attribute on exceptions. ``PyErr_NormalizeException`` will
|
||||
always set the ``traceback`` attribute to its ``tb`` argument and the
|
||||
``__context__`` and ``__cause__`` attributes to ``None``.
|
||||
|
||||
A new API function, PyErr_SetContext(context), will help C
|
||||
programmers provide chained exception information. This function
|
||||
will first normalize the current exception so it is an instance,
|
||||
then set its '__context__' attribute. A similar API function,
|
||||
PyErr_SetCause(cause), will set the '__cause__' attribute.
|
||||
A new API function, ``PyErr_SetContext(context)``, will help C programmers
|
||||
provide chained exception information. This function will first normalize the
|
||||
current exception so it is an instance, then set its ``__context__`` attribute.
|
||||
A similar API function, ``PyErr_SetCause(cause)``, will set the ``__cause__``
|
||||
attribute.
|
||||
|
||||
|
||||
Compatibility
|
||||
=============
|
||||
|
||||
Chained exceptions expose the type of the most recent exception, so
|
||||
they will still match the same 'except' clauses as they do now.
|
||||
Chained exceptions expose the type of the most recent exception, so they will
|
||||
still match the same ``except`` clauses as they do now.
|
||||
|
||||
The proposed changes should not break any code unless it sets or
|
||||
uses attributes named '__context__', '__cause__', or '__traceback__'
|
||||
on exception instances. As of 2005-05-12, the Python standard
|
||||
library contains no mention of such attributes.
|
||||
The proposed changes should not break any code unless it sets or uses
|
||||
attributes named ``__context__``, ``__cause__``, or ``__traceback__`` on
|
||||
exception instances. As of 2005-05-12, the Python standard library contains no
|
||||
mention of such attributes.
|
||||
|
||||
|
||||
Open Issue: Extra Information
|
||||
==============================
|
||||
|
||||
Walter Dörwald [11] expressed a desire to attach extra information
|
||||
to an exception during its upward propagation without changing its
|
||||
type. This could be a useful feature, but it is not addressed by
|
||||
this PEP. It could conceivably be addressed by a separate PEP
|
||||
establishing conventions for other informational attributes on
|
||||
exceptions.
|
||||
Walter Dörwald [11]_ expressed a desire to attach extra information to an
|
||||
exception during its upward propagation without changing its type. This could
|
||||
be a useful feature, but it is not addressed by this PEP. It could conceivably
|
||||
be addressed by a separate PEP establishing conventions for other informational
|
||||
attributes on exceptions.
|
||||
|
||||
|
||||
Open Issue: Suppressing Context
|
||||
================================
|
||||
|
||||
As written, this PEP makes it impossible to suppress '__context__',
|
||||
since setting exc.__context__ to None in an 'except' or 'finally'
|
||||
clause will only result in it being set again when exc is raised.
|
||||
As written, this PEP makes it impossible to suppress ``__context__``, since
|
||||
setting ``exc.__context__`` to ``None`` in an ``except`` or ``finally`` clause
|
||||
will only result in it being set again when ``exc`` is raised.
|
||||
|
||||
|
||||
Open Issue: Limiting Exception Types
|
||||
=====================================
|
||||
|
||||
To improve encapsulation, library implementors may want to wrap all
|
||||
implementation-level exceptions with an application-level exception.
|
||||
One could try to wrap exceptions by writing this:
|
||||
To improve encapsulation, library implementors may want to wrap all
|
||||
implementation-level exceptions with an application-level exception. One could
|
||||
try to wrap exceptions by writing this::
|
||||
|
||||
try:
|
||||
... implementation may raise an exception ...
|
||||
|
@ -375,16 +388,16 @@ Open Issue: Limiting Exception Types
|
|||
import sys
|
||||
raise ApplicationError from sys.exc_value
|
||||
|
||||
or this:
|
||||
or this::
|
||||
|
||||
try:
|
||||
... implementation may raise an exception ...
|
||||
except Exception, exc:
|
||||
raise ApplicationError from exc
|
||||
|
||||
but both are somewhat flawed. It would be nice to be able to name
|
||||
the current exception in a catch-all 'except' clause, but that isn't
|
||||
addressed here. Such a feature would allow something like this:
|
||||
but both are somewhat flawed. It would be nice to be able to name the current
|
||||
exception in a catch-all ``except`` clause, but that isn't addressed here.
|
||||
Such a feature would allow something like this::
|
||||
|
||||
try:
|
||||
... implementation may raise an exception ...
|
||||
|
@ -393,11 +406,12 @@ Open Issue: Limiting Exception Types
|
|||
|
||||
|
||||
Open Issue: yield
|
||||
==================
|
||||
|
||||
The exception context is lost when a 'yield' statement is executed;
|
||||
resuming the frame after the 'yield' does not restore the context.
|
||||
Addressing this problem is out of the scope of this PEP; it is not a
|
||||
new problem, as demonstrated by the following example:
|
||||
The exception context is lost when a ``yield`` statement is executed; resuming
|
||||
the frame after the ``yield`` does not restore the context. Addressing this
|
||||
problem is out of the scope of this PEP; it is not a new problem, as
|
||||
demonstrated by the following example::
|
||||
|
||||
>>> def gen():
|
||||
... try:
|
||||
|
@ -415,140 +429,150 @@ Open Issue: yield
|
|||
|
||||
|
||||
Open Issue: Garbage Collection
|
||||
===============================
|
||||
|
||||
The strongest objection to this proposal has been that it creates
|
||||
cycles between exceptions and stack frames [12]. Collection of
|
||||
cyclic garbage (and therefore resource release) can be greatly
|
||||
delayed.
|
||||
The strongest objection to this proposal has been that it creates cycles
|
||||
between exceptions and stack frames [12]_. Collection of cyclic garbage (and
|
||||
therefore resource release) can be greatly delayed.
|
||||
|
||||
::
|
||||
|
||||
>>> try:
|
||||
>>> 1/0
|
||||
>>> except Exception, err:
|
||||
>>> pass
|
||||
|
||||
will introduce a cycle from err -> traceback -> stack frame -> err,
|
||||
keeping all locals in the same scope alive until the next GC happens.
|
||||
will introduce a cycle from err -> traceback -> stack frame -> err, keeping all
|
||||
locals in the same scope alive until the next GC happens.
|
||||
|
||||
Today, these locals would go out of scope. There is lots of code
|
||||
which assumes that "local" resources -- particularly open files -- will
|
||||
be closed quickly. If closure has to wait for the next GC, a program
|
||||
(which runs fine today) may run out of file handles.
|
||||
Today, these locals would go out of scope. There is lots of code which assumes
|
||||
that "local" resources -- particularly open files -- will be closed quickly.
|
||||
If closure has to wait for the next GC, a program (which runs fine today) may
|
||||
run out of file handles.
|
||||
|
||||
Making the __traceback__ attribute a weak reference would avoid the
|
||||
problems with cyclic garbage. Unfortunately, it would make saving
|
||||
the Exception for later (as unittest does) more awkward, and it would
|
||||
not allow as much cleanup of the sys module.
|
||||
Making the ``__traceback__`` attribute a weak reference would avoid the
|
||||
problems with cyclic garbage. Unfortunately, it would make saving the
|
||||
``Exception`` for later (as ``unittest`` does) more awkward, and it would not
|
||||
allow as much cleanup of the ``sys`` module.
|
||||
|
||||
A possible alternate solution, suggested by Adam Olsen, would be to
|
||||
instead turn the reference from the stack frame to the 'err' variable
|
||||
into a weak reference when the variable goes out of scope [13].
|
||||
A possible alternate solution, suggested by Adam Olsen, would be to instead
|
||||
turn the reference from the stack frame to the ``err`` variable into a weak
|
||||
reference when the variable goes out of scope [13]_.
|
||||
|
||||
|
||||
Possible Future Compatible Changes
|
||||
==================================
|
||||
|
||||
These changes are consistent with the appearance of exceptions as
|
||||
a single object rather than a triple at the interpreter level.
|
||||
These changes are consistent with the appearance of exceptions as a single
|
||||
object rather than a triple at the interpreter level.
|
||||
|
||||
- If PEP 340 or PEP 343 is accepted, replace the three (type, value,
|
||||
traceback) arguments to __exit__ with a single exception argument.
|
||||
- If PEP 340 or PEP 343 is accepted, replace the three (``type``, ``value``,
|
||||
``traceback``) arguments to ``__exit__`` with a single exception argument.
|
||||
|
||||
- Deprecate sys.exc_type, sys.exc_value, sys.exc_traceback, and
|
||||
sys.exc_info() in favour of a single member, sys.exception.
|
||||
- Deprecate ``sys.exc_type``, ``sys.exc_value``, ``sys.exc_traceback``, and
|
||||
``sys.exc_info()`` in favour of a single member, ``sys.exception``.
|
||||
|
||||
- Deprecate sys.last_type, sys.last_value, and sys.last_traceback
|
||||
in favour of a single member, sys.last_exception.
|
||||
- Deprecate ``sys.last_type``, ``sys.last_value``, and ``sys.last_traceback``
|
||||
in favour of a single member, ``sys.last_exception``.
|
||||
|
||||
- Deprecate the three-argument form of the 'raise' statement in
|
||||
favour of the one-argument form.
|
||||
- Deprecate the three-argument form of the ``raise`` statement in favour of the
|
||||
one-argument form.
|
||||
|
||||
- Upgrade cgitb.html() to accept a single value as its first
|
||||
argument as an alternative to a (type, value, traceback) tuple.
|
||||
- Upgrade ``cgitb.html()`` to accept a single value as its first argument as an
|
||||
alternative to a ``(type, value, traceback)`` tuple.
|
||||
|
||||
|
||||
Possible Future Incompatible Changes
|
||||
====================================
|
||||
|
||||
These changes might be worth considering for Python 3000.
|
||||
These changes might be worth considering for Python 3000.
|
||||
|
||||
- Remove sys.exc_type, sys.exc_value, sys.exc_traceback, and
|
||||
sys.exc_info().
|
||||
- Remove ``sys.exc_type``, ``sys.exc_value``, ``sys.exc_traceback``, and
|
||||
``sys.exc_info()``.
|
||||
|
||||
- Remove sys.last_type, sys.last_value, and sys.last_traceback.
|
||||
- Remove ``sys.last_type``, ``sys.last_value``, and ``sys.last_traceback``.
|
||||
|
||||
- Replace the three-argument sys.excepthook with a one-argument
|
||||
API, and changing the 'cgitb' module to match.
|
||||
- Replace the three-argument ``sys.excepthook`` with a one-argument API, and
|
||||
changing the ``cgitb`` module to match.
|
||||
|
||||
- Remove the three-argument form of the 'raise' statement.
|
||||
- Remove the three-argument form of the ``raise`` statement.
|
||||
|
||||
- Upgrade traceback.print_exception to accept an 'exception'
|
||||
argument instead of the type, value, and traceback arguments.
|
||||
- Upgrade ``traceback.print_exception`` to accept an ``exception`` argument
|
||||
instead of the ``type``, ``value``, and ``traceback`` arguments.
|
||||
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
||||
The __traceback__ and __cause__ attributes and the new raise syntax were
|
||||
implemented in revision 57783 [14].
|
||||
The ``__traceback__`` and ``__cause__`` attributes and the new raise syntax
|
||||
were implemented in revision 57783 [14]_.
|
||||
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
Brett Cannon, Greg Ewing, Guido van Rossum, Jeremy Hylton, Phillip
|
||||
J. Eby, Raymond Hettinger, Walter Dörwald, and others.
|
||||
Brett Cannon, Greg Ewing, Guido van Rossum, Jeremy Hylton, Phillip J. Eby,
|
||||
Raymond Hettinger, Walter Dörwald, and others.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
[1] Raymond Hettinger, "Idea for avoiding exception masking"
|
||||
.. [1] Raymond Hettinger, "Idea for avoiding exception masking"
|
||||
http://mail.python.org/pipermail/python-dev/2003-January/032492.html
|
||||
|
||||
[2] Brett Cannon explains chained exceptions
|
||||
.. [2] Brett Cannon explains chained exceptions
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036063.html
|
||||
|
||||
[3] Greg Ewing points out masking caused by exceptions during finally
|
||||
.. [3] Greg Ewing points out masking caused by exceptions during finally
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036290.html
|
||||
|
||||
[4] Greg Ewing suggests storing the traceback in the exception object
|
||||
.. [4] Greg Ewing suggests storing the traceback in the exception object
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036092.html
|
||||
|
||||
[5] Guido van Rossum mentions exceptions having a traceback attribute
|
||||
.. [5] Guido van Rossum mentions exceptions having a traceback attribute
|
||||
http://mail.python.org/pipermail/python-dev/2005-April/053060.html
|
||||
|
||||
[6] Ka-Ping Yee, "Tidier Exceptions"
|
||||
.. [6] Ka-Ping Yee, "Tidier Exceptions"
|
||||
http://mail.python.org/pipermail/python-dev/2005-May/053671.html
|
||||
|
||||
[7] Ka-Ping Yee, "Chained Exceptions"
|
||||
.. [7] Ka-Ping Yee, "Chained Exceptions"
|
||||
http://mail.python.org/pipermail/python-dev/2005-May/053672.html
|
||||
|
||||
[8] Guido van Rossum discusses automatic chaining in PyErr_Set*
|
||||
.. [8] Guido van Rossum discusses automatic chaining in ``PyErr_Set*``
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036180.html
|
||||
|
||||
[9] Tony Olensky, "Omnibus Structured Exception/Error Handling Mechanism"
|
||||
.. [9] Tony Olensky, "Omnibus Structured Exception/Error Handling Mechanism"
|
||||
http://dev.perl.org/perl6/rfc/88.html
|
||||
|
||||
[10] MSDN .NET Framework Library, "Exception.InnerException Property"
|
||||
.. [10] MSDN .NET Framework Library, "Exception.InnerException Property"
|
||||
http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemexceptionclassinnerexceptiontopic.asp
|
||||
|
||||
[11] Walter Dörwald suggests wrapping exceptions to add details
|
||||
.. [11] Walter Dörwald suggests wrapping exceptions to add details
|
||||
http://mail.python.org/pipermail/python-dev/2003-June/036148.html
|
||||
|
||||
[12] Guido van Rossum restates the objection to cyclic trash
|
||||
.. [12] Guido van Rossum restates the objection to cyclic trash
|
||||
http://mail.python.org/pipermail/python-3000/2007-January/005322.html
|
||||
|
||||
[13] Adam Olsen suggests using a weakref from stack frame to exception
|
||||
.. [13] Adam Olsen suggests using a weakref from stack frame to exception
|
||||
http://mail.python.org/pipermail/python-3000/2007-January/005363.html
|
||||
|
||||
[14] Patch to implement the bulk of the PEP
|
||||
.. [14] Patch to implement the bulk of the PEP
|
||||
http://svn.python.org/view/python/branches/py3k/Include/?rev=57783&view=rev
|
||||
|
||||
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This document has been placed in the public domain.
|
||||
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
|
||||
coding: utf-8
|
||||
End:
|
||||
|
||||
..
|
||||
Local Variables:
|
||||
mode: indented-text
|
||||
indent-tabs-mode: nil
|
||||
sentence-end-double-space: t
|
||||
fill-column: 70
|
||||
coding: utf-8
|
||||
End:
|
||||
|
|
Loading…
Reference in New Issue