[Python-checkins] gh-104770: Let generator.close() return value (#104771)
gvanrossum
webhook-mailer at python.org
Tue May 23 16:52:03 EDT 2023
https://github.com/python/cpython/commit/d56c933992c86986bd58eb3880aed0ed1b0cadc9
commit: d56c933992c86986bd58eb3880aed0ed1b0cadc9
branch: main
author: Nicolas Tessore <n.tessore at ucl.ac.uk>
committer: gvanrossum <gvanrossum at gmail.com>
date: 2023-05-23T13:51:56-07:00
summary:
gh-104770: Let generator.close() return value (#104771)
Co-authored-by: Irit Katriel <1055913+iritkatriel at users.noreply.github.com>
files:
A Misc/NEWS.d/next/Core and Builtins/2023-05-23-00-36-02.gh-issue-104770.poSkyY.rst
M Doc/reference/expressions.rst
M Lib/test/test_generators.py
M Objects/genobject.c
diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst
index b97a08f25d92..0c700f908d68 100644
--- a/Doc/reference/expressions.rst
+++ b/Doc/reference/expressions.rst
@@ -595,12 +595,19 @@ is already executing raises a :exc:`ValueError` exception.
.. method:: generator.close()
Raises a :exc:`GeneratorExit` at the point where the generator function was
- paused. If the generator function then exits gracefully, is already closed,
- or raises :exc:`GeneratorExit` (by not catching the exception), close
- returns to its caller. If the generator yields a value, a
- :exc:`RuntimeError` is raised. If the generator raises any other exception,
- it is propagated to the caller. :meth:`close` does nothing if the generator
- has already exited due to an exception or normal exit.
+ paused. If the generator function catches the exception and returns a
+ value, this value is returned from :meth:`close`. If the generator function
+ is already closed, or raises :exc:`GeneratorExit` (by not catching the
+ exception), :meth:`close` returns :const:`None`. If the generator yields a
+ value, a :exc:`RuntimeError` is raised. If the generator raises any other
+ 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
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index 31680b5a92e0..a8a344ab8de4 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -451,6 +451,88 @@ def g():
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):
def test_exception_context_with_yield(self):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-05-23-00-36-02.gh-issue-104770.poSkyY.rst b/Misc/NEWS.d/next/Core and Builtins/2023-05-23-00-36-02.gh-issue-104770.poSkyY.rst
new file mode 100644
index 000000000000..2103fb7d61c2
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2023-05-23-00-36-02.gh-issue-104770.poSkyY.rst
@@ -0,0 +1,2 @@
+If a generator returns a value upon being closed, the value is now returned
+by :meth:`generator.close`.
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 9252c6549345..1abfc83ab678 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -408,11 +408,16 @@ gen_close(PyGenObject *gen, PyObject *args)
PyErr_SetString(PyExc_RuntimeError, msg);
return NULL;
}
- if (PyErr_ExceptionMatches(PyExc_StopIteration)
- || PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
- PyErr_Clear(); /* ignore these errors */
+ assert(PyErr_Occurred());
+ if (PyErr_ExceptionMatches(PyExc_GeneratorExit)) {
+ PyErr_Clear(); /* ignore this error */
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;
}
More information about the Python-checkins
mailing list