mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
PEP 479: Change StopIteration handling inside generators.
Closes issue #22906.
This commit is contained in:
parent
bd60e8dece
commit
8170e8c0d1
14 changed files with 103 additions and 15 deletions
|
@ -481,10 +481,10 @@ Here's a sample usage of the ``generate_ints()`` generator:
|
||||||
You could equally write ``for i in generate_ints(5)``, or ``a,b,c =
|
You could equally write ``for i in generate_ints(5)``, or ``a,b,c =
|
||||||
generate_ints(3)``.
|
generate_ints(3)``.
|
||||||
|
|
||||||
Inside a generator function, ``return value`` is semantically equivalent to
|
Inside a generator function, ``return value`` causes ``StopIteration(value)``
|
||||||
``raise StopIteration(value)``. If no value is returned or the bottom of the
|
to be raised from the :meth:`~generator.__next__` method. Once this happens, or
|
||||||
function is reached, the procession of values ends and the generator cannot
|
the bottom of the function is reached, the procession of values ends and the
|
||||||
return any further values.
|
generator cannot yield any further values.
|
||||||
|
|
||||||
You could achieve the effect of generators manually by writing your own class
|
You could achieve the effect of generators manually by writing your own class
|
||||||
and storing all the local variables of the generator as instance variables. For
|
and storing all the local variables of the generator as instance variables. For
|
||||||
|
|
|
@ -87,6 +87,9 @@ language using this mechanism:
|
||||||
| unicode_literals | 2.6.0a2 | 3.0 | :pep:`3112`: |
|
| unicode_literals | 2.6.0a2 | 3.0 | :pep:`3112`: |
|
||||||
| | | | *Bytes literals in Python 3000* |
|
| | | | *Bytes literals in Python 3000* |
|
||||||
+------------------+-------------+--------------+---------------------------------------------+
|
+------------------+-------------+--------------+---------------------------------------------+
|
||||||
|
| generator_stop | 3.5.0b1 | 3.7 | :pep:`479`: |
|
||||||
|
| | | | *StopIteration handling inside generators* |
|
||||||
|
+------------------+-------------+--------------+---------------------------------------------+
|
||||||
|
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
|
@ -310,10 +310,18 @@ The following exceptions are the exceptions that are usually raised.
|
||||||
raised, and the value returned by the function is used as the
|
raised, and the value returned by the function is used as the
|
||||||
:attr:`value` parameter to the constructor of the exception.
|
:attr:`value` parameter to the constructor of the exception.
|
||||||
|
|
||||||
|
If a generator function defined in the presence of a ``from __future__
|
||||||
|
import generator_stop`` directive raises :exc:`StopIteration`, it will be
|
||||||
|
converted into a :exc:`RuntimeError` (retaining the :exc:`StopIteration`
|
||||||
|
as the new exception's cause).
|
||||||
|
|
||||||
.. versionchanged:: 3.3
|
.. versionchanged:: 3.3
|
||||||
Added ``value`` attribute and the ability for generator functions to
|
Added ``value`` attribute and the ability for generator functions to
|
||||||
use it to return a value.
|
use it to return a value.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.5
|
||||||
|
Introduced the RuntimeError transformation.
|
||||||
|
|
||||||
.. exception:: SyntaxError
|
.. exception:: SyntaxError
|
||||||
|
|
||||||
Raised when the parser encounters a syntax error. This may occur in an
|
Raised when the parser encounters a syntax error. This may occur in an
|
||||||
|
|
|
@ -443,12 +443,12 @@ is already executing raises a :exc:`ValueError` exception.
|
||||||
.. method:: generator.close()
|
.. method:: generator.close()
|
||||||
|
|
||||||
Raises a :exc:`GeneratorExit` at the point where the generator function was
|
Raises a :exc:`GeneratorExit` at the point where the generator function was
|
||||||
paused. If the generator function then raises :exc:`StopIteration` (by
|
paused. If the generator function then exits gracefully, is already closed,
|
||||||
exiting normally, or due to already being closed) or :exc:`GeneratorExit` (by
|
or raises :exc:`GeneratorExit` (by not catching the exception), close
|
||||||
not catching the exception), close returns to its caller. If the generator
|
returns to its caller. If the generator yields a value, a
|
||||||
yields a value, a :exc:`RuntimeError` is raised. If the generator raises any
|
:exc:`RuntimeError` is raised. If the generator raises any other exception,
|
||||||
other exception, it is propagated to the caller. :meth:`close` does nothing
|
it is propagated to the caller. :meth:`close` does nothing if the generator
|
||||||
if the generator has already exited due to an exception or normal exit.
|
has already exited due to an exception or normal exit.
|
||||||
|
|
||||||
.. index:: single: yield; examples
|
.. index:: single: yield; examples
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ typedef struct {
|
||||||
#define CO_FUTURE_UNICODE_LITERALS 0x20000
|
#define CO_FUTURE_UNICODE_LITERALS 0x20000
|
||||||
|
|
||||||
#define CO_FUTURE_BARRY_AS_BDFL 0x40000
|
#define CO_FUTURE_BARRY_AS_BDFL 0x40000
|
||||||
|
#define CO_FUTURE_GENERATOR_STOP 0x80000
|
||||||
|
|
||||||
/* This value is found in the co_cell2arg array when the associated cell
|
/* This value is found in the co_cell2arg array when the associated cell
|
||||||
variable does not correspond to an argument. The maximum number of
|
variable does not correspond to an argument. The maximum number of
|
||||||
|
|
|
@ -27,6 +27,7 @@ typedef struct {
|
||||||
#define FUTURE_PRINT_FUNCTION "print_function"
|
#define FUTURE_PRINT_FUNCTION "print_function"
|
||||||
#define FUTURE_UNICODE_LITERALS "unicode_literals"
|
#define FUTURE_UNICODE_LITERALS "unicode_literals"
|
||||||
#define FUTURE_BARRY_AS_BDFL "barry_as_FLUFL"
|
#define FUTURE_BARRY_AS_BDFL "barry_as_FLUFL"
|
||||||
|
#define FUTURE_GENERATOR_STOP "generator_stop"
|
||||||
|
|
||||||
struct _mod; /* Declare the existence of this type */
|
struct _mod; /* Declare the existence of this type */
|
||||||
#define PyAST_Compile(mod, s, f, ar) PyAST_CompileEx(mod, s, f, -1, ar)
|
#define PyAST_Compile(mod, s, f, ar) PyAST_CompileEx(mod, s, f, -1, ar)
|
||||||
|
|
|
@ -9,7 +9,8 @@ extern "C" {
|
||||||
|
|
||||||
#define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | \
|
#define PyCF_MASK (CO_FUTURE_DIVISION | CO_FUTURE_ABSOLUTE_IMPORT | \
|
||||||
CO_FUTURE_WITH_STATEMENT | CO_FUTURE_PRINT_FUNCTION | \
|
CO_FUTURE_WITH_STATEMENT | CO_FUTURE_PRINT_FUNCTION | \
|
||||||
CO_FUTURE_UNICODE_LITERALS | CO_FUTURE_BARRY_AS_BDFL)
|
CO_FUTURE_UNICODE_LITERALS | CO_FUTURE_BARRY_AS_BDFL | \
|
||||||
|
CO_FUTURE_GENERATOR_STOP)
|
||||||
#define PyCF_MASK_OBSOLETE (CO_NESTED)
|
#define PyCF_MASK_OBSOLETE (CO_NESTED)
|
||||||
#define PyCF_SOURCE_IS_UTF8 0x0100
|
#define PyCF_SOURCE_IS_UTF8 0x0100
|
||||||
#define PyCF_DONT_IMPLY_DEDENT 0x0200
|
#define PyCF_DONT_IMPLY_DEDENT 0x0200
|
||||||
|
|
|
@ -56,6 +56,7 @@ all_feature_names = [
|
||||||
"print_function",
|
"print_function",
|
||||||
"unicode_literals",
|
"unicode_literals",
|
||||||
"barry_as_FLUFL",
|
"barry_as_FLUFL",
|
||||||
|
"generator_stop",
|
||||||
]
|
]
|
||||||
|
|
||||||
__all__ = ["all_feature_names"] + all_feature_names
|
__all__ = ["all_feature_names"] + all_feature_names
|
||||||
|
@ -72,6 +73,7 @@ CO_FUTURE_WITH_STATEMENT = 0x8000 # with statement
|
||||||
CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function
|
CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function
|
||||||
CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals
|
CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals
|
||||||
CO_FUTURE_BARRY_AS_BDFL = 0x40000
|
CO_FUTURE_BARRY_AS_BDFL = 0x40000
|
||||||
|
CO_FUTURE_GENERATOR_STOP = 0x80000 # StopIteration becomes RuntimeError in generators
|
||||||
|
|
||||||
class _Feature:
|
class _Feature:
|
||||||
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
|
def __init__(self, optionalRelease, mandatoryRelease, compiler_flag):
|
||||||
|
@ -132,3 +134,7 @@ unicode_literals = _Feature((2, 6, 0, "alpha", 2),
|
||||||
barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2),
|
barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2),
|
||||||
(3, 9, 0, "alpha", 0),
|
(3, 9, 0, "alpha", 0),
|
||||||
CO_FUTURE_BARRY_AS_BDFL)
|
CO_FUTURE_BARRY_AS_BDFL)
|
||||||
|
|
||||||
|
generator_stop = _Feature((3, 5, 0, "beta", 1),
|
||||||
|
(3, 7, 0, "alpha", 0),
|
||||||
|
CO_FUTURE_GENERATOR_STOP)
|
||||||
|
|
|
@ -77,10 +77,17 @@ class _GeneratorContextManager(ContextDecorator):
|
||||||
self.gen.throw(type, value, traceback)
|
self.gen.throw(type, value, traceback)
|
||||||
raise RuntimeError("generator didn't stop after throw()")
|
raise RuntimeError("generator didn't stop after throw()")
|
||||||
except StopIteration as exc:
|
except StopIteration as exc:
|
||||||
# Suppress the exception *unless* it's the same exception that
|
# Suppress StopIteration *unless* it's the same exception that
|
||||||
# was passed to throw(). This prevents a StopIteration
|
# was passed to throw(). This prevents a StopIteration
|
||||||
# raised inside the "with" statement from being suppressed
|
# raised inside the "with" statement from being suppressed.
|
||||||
return exc is not value
|
return exc is not value
|
||||||
|
except RuntimeError as exc:
|
||||||
|
# Likewise, avoid suppressing if a StopIteration exception
|
||||||
|
# was passed to throw() and later wrapped into a RuntimeError
|
||||||
|
# (see PEP 479).
|
||||||
|
if exc.__cause__ is value:
|
||||||
|
return False
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
# only re-raise if it's *not* the exception that was
|
# only re-raise if it's *not* the exception that was
|
||||||
# passed to throw(), because __exit__() must not raise
|
# passed to throw(), because __exit__() must not raise
|
||||||
|
|
|
@ -1596,8 +1596,7 @@ def _mdiff(fromlines, tolines, context=None, linejunk=None,
|
||||||
# them up without doing anything else with them.
|
# them up without doing anything else with them.
|
||||||
line_pair_iterator = _line_pair_iterator()
|
line_pair_iterator = _line_pair_iterator()
|
||||||
if context is None:
|
if context is None:
|
||||||
while True:
|
yield from line_pair_iterator
|
||||||
yield next(line_pair_iterator)
|
|
||||||
# Handle case where user wants context differencing. We must do some
|
# Handle case where user wants context differencing. We must do some
|
||||||
# storage of lines until we know for sure that they are to be yielded.
|
# storage of lines until we know for sure that they are to be yielded.
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -83,6 +83,40 @@ class ContextManagerTestCase(unittest.TestCase):
|
||||||
raise ZeroDivisionError(999)
|
raise ZeroDivisionError(999)
|
||||||
self.assertEqual(state, [1, 42, 999])
|
self.assertEqual(state, [1, 42, 999])
|
||||||
|
|
||||||
|
def test_contextmanager_except_stopiter(self):
|
||||||
|
stop_exc = StopIteration('spam')
|
||||||
|
@contextmanager
|
||||||
|
def woohoo():
|
||||||
|
yield
|
||||||
|
try:
|
||||||
|
with woohoo():
|
||||||
|
raise stop_exc
|
||||||
|
except Exception as ex:
|
||||||
|
self.assertIs(ex, stop_exc)
|
||||||
|
else:
|
||||||
|
self.fail('StopIteration was suppressed')
|
||||||
|
|
||||||
|
def test_contextmanager_except_pep479(self):
|
||||||
|
code = """\
|
||||||
|
from __future__ import generator_stop
|
||||||
|
from contextlib import contextmanager
|
||||||
|
@contextmanager
|
||||||
|
def woohoo():
|
||||||
|
yield
|
||||||
|
"""
|
||||||
|
locals = {}
|
||||||
|
exec(code, locals, locals)
|
||||||
|
woohoo = locals['woohoo']
|
||||||
|
|
||||||
|
stop_exc = StopIteration('spam')
|
||||||
|
try:
|
||||||
|
with woohoo():
|
||||||
|
raise stop_exc
|
||||||
|
except Exception as ex:
|
||||||
|
self.assertIs(ex, stop_exc)
|
||||||
|
else:
|
||||||
|
self.fail('StopIteration was suppressed')
|
||||||
|
|
||||||
def _create_contextmanager_attribs(self):
|
def _create_contextmanager_attribs(self):
|
||||||
def attribs(**kw):
|
def attribs(**kw):
|
||||||
def decorate(func):
|
def decorate(func):
|
||||||
|
|
|
@ -33,6 +33,8 @@ Core and Builtins
|
||||||
|
|
||||||
- Issue #9951: Added a hex() method to bytes, bytearray, and memoryview.
|
- Issue #9951: Added a hex() method to bytes, bytearray, and memoryview.
|
||||||
|
|
||||||
|
- Issue #22906: PEP 479: Change StopIteration handling inside generators.
|
||||||
|
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,30 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
|
||||||
}
|
}
|
||||||
Py_CLEAR(result);
|
Py_CLEAR(result);
|
||||||
}
|
}
|
||||||
|
else if (!result) {
|
||||||
|
/* Check for __future__ generator_stop and conditionally turn
|
||||||
|
* a leaking StopIteration into RuntimeError (with its cause
|
||||||
|
* set appropriately). */
|
||||||
|
if ((((PyCodeObject *)gen->gi_code)->co_flags &
|
||||||
|
CO_FUTURE_GENERATOR_STOP)
|
||||||
|
&& PyErr_ExceptionMatches(PyExc_StopIteration))
|
||||||
|
{
|
||||||
|
PyObject *exc, *val, *val2, *tb;
|
||||||
|
PyErr_Fetch(&exc, &val, &tb);
|
||||||
|
PyErr_NormalizeException(&exc, &val, &tb);
|
||||||
|
if (tb != NULL)
|
||||||
|
PyException_SetTraceback(val, tb);
|
||||||
|
Py_DECREF(exc);
|
||||||
|
Py_XDECREF(tb);
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"generator raised StopIteration");
|
||||||
|
PyErr_Fetch(&exc, &val2, &tb);
|
||||||
|
PyErr_NormalizeException(&exc, &val2, &tb);
|
||||||
|
PyException_SetCause(val2, val);
|
||||||
|
PyException_SetContext(val2, val);
|
||||||
|
PyErr_Restore(exc, val2, tb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!result || f->f_stacktop == NULL) {
|
if (!result || f->f_stacktop == NULL) {
|
||||||
/* generator can't be rerun, so release the frame */
|
/* generator can't be rerun, so release the frame */
|
||||||
|
|
|
@ -40,6 +40,8 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
|
||||||
continue;
|
continue;
|
||||||
} else if (strcmp(feature, FUTURE_BARRY_AS_BDFL) == 0) {
|
} else if (strcmp(feature, FUTURE_BARRY_AS_BDFL) == 0) {
|
||||||
ff->ff_features |= CO_FUTURE_BARRY_AS_BDFL;
|
ff->ff_features |= CO_FUTURE_BARRY_AS_BDFL;
|
||||||
|
} else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) {
|
||||||
|
ff->ff_features |= CO_FUTURE_GENERATOR_STOP;
|
||||||
} else if (strcmp(feature, "braces") == 0) {
|
} else if (strcmp(feature, "braces") == 0) {
|
||||||
PyErr_SetString(PyExc_SyntaxError,
|
PyErr_SetString(PyExc_SyntaxError,
|
||||||
"not a chance");
|
"not a chance");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue