mirror of https://github.com/python/cpython
gh-104770: Let generator.close() return value (#104771)
Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
This commit is contained in:
parent
50fce89d12
commit
d56c933992
|
@ -595,12 +595,19 @@ 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 exits gracefully, is already closed,
|
paused. If the generator function catches the exception and returns a
|
||||||
or raises :exc:`GeneratorExit` (by not catching the exception), close
|
value, this value is returned from :meth:`close`. If the generator function
|
||||||
returns to its caller. If the generator yields a value, a
|
is already closed, or raises :exc:`GeneratorExit` (by not catching the
|
||||||
:exc:`RuntimeError` is raised. If the generator raises any other exception,
|
exception), :meth:`close` returns :const:`None`. If the generator yields a
|
||||||
it is propagated to the caller. :meth:`close` does nothing if the generator
|
value, a :exc:`RuntimeError` is raised. If the generator raises any other
|
||||||
has already exited due to an exception or normal exit.
|
exception, it is propagated to the caller. If the generator has already
|
||||||
|
exited due to an exception or normal exit, :meth:`close` returns
|
||||||
|
:const:`None` and has no other effect.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.13
|
||||||
|
|
||||||
|
If a generator returns a value upon being closed, the value is returned
|
||||||
|
by :meth:`close`.
|
||||||
|
|
||||||
.. index:: single: yield; examples
|
.. index:: single: yield; examples
|
||||||
|
|
||||||
|
|
|
@ -451,6 +451,88 @@ class ExceptionTest(unittest.TestCase):
|
||||||
self.assertEqual(cm.exception.value.value, 2)
|
self.assertEqual(cm.exception.value.value, 2)
|
||||||
|
|
||||||
|
|
||||||
|
class GeneratorCloseTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_close_no_return_value(self):
|
||||||
|
def f():
|
||||||
|
yield
|
||||||
|
|
||||||
|
gen = f()
|
||||||
|
gen.send(None)
|
||||||
|
self.assertIsNone(gen.close())
|
||||||
|
|
||||||
|
def test_close_return_value(self):
|
||||||
|
def f():
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
# close() raises GeneratorExit here, which is caught
|
||||||
|
except GeneratorExit:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
gen = f()
|
||||||
|
gen.send(None)
|
||||||
|
self.assertEqual(gen.close(), 0)
|
||||||
|
|
||||||
|
def test_close_not_catching_exit(self):
|
||||||
|
def f():
|
||||||
|
yield
|
||||||
|
# close() raises GeneratorExit here, which isn't caught and
|
||||||
|
# therefore propagates -- no return value
|
||||||
|
return 0
|
||||||
|
|
||||||
|
gen = f()
|
||||||
|
gen.send(None)
|
||||||
|
self.assertIsNone(gen.close())
|
||||||
|
|
||||||
|
def test_close_not_started(self):
|
||||||
|
def f():
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except GeneratorExit:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
gen = f()
|
||||||
|
self.assertIsNone(gen.close())
|
||||||
|
|
||||||
|
def test_close_exhausted(self):
|
||||||
|
def f():
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except GeneratorExit:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
gen = f()
|
||||||
|
next(gen)
|
||||||
|
with self.assertRaises(StopIteration):
|
||||||
|
next(gen)
|
||||||
|
self.assertIsNone(gen.close())
|
||||||
|
|
||||||
|
def test_close_closed(self):
|
||||||
|
def f():
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except GeneratorExit:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
gen = f()
|
||||||
|
gen.send(None)
|
||||||
|
self.assertEqual(gen.close(), 0)
|
||||||
|
self.assertIsNone(gen.close())
|
||||||
|
|
||||||
|
def test_close_raises(self):
|
||||||
|
def f():
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
except GeneratorExit:
|
||||||
|
pass
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
gen = f()
|
||||||
|
gen.send(None)
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
gen.close()
|
||||||
|
|
||||||
|
|
||||||
class GeneratorThrowTest(unittest.TestCase):
|
class GeneratorThrowTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_exception_context_with_yield(self):
|
def test_exception_context_with_yield(self):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
If a generator returns a value upon being closed, the value is now returned
|
||||||
|
by :meth:`generator.close`.
|
|
@ -408,11 +408,16 @@ gen_close(PyGenObject *gen, PyObject *args)
|
||||||
PyErr_SetString(PyExc_RuntimeError, msg);
|
PyErr_SetString(PyExc_RuntimeError, msg);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (PyErr_ExceptionMatches(PyExc_StopIteration)
|
assert(PyErr_Occurred());
|
||||||
|| PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
|
if (PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
|
||||||
PyErr_Clear(); /* ignore these errors */
|
PyErr_Clear(); /* ignore this error */
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
/* if the generator returned a value while closing, StopIteration was
|
||||||
|
* raised in gen_send_ex() above; retrieve and return the value here */
|
||||||
|
if (_PyGen_FetchStopIterationValue(&retval) == 0) {
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue