gh-96348: Deprecate the 3-arg signature of coroutine.throw and generator.throw (GH-96428)
https://github.com/python/cpython/commit/83a3de4e0632d90e0d1d5a9b8859a94c9ac... commit: 83a3de4e0632d90e0d1d5a9b8859a94c9ac25f65 branch: main author: Ofey Chan <ofey206@gmail.com> committer: iritkatriel <1055913+iritkatriel@users.noreply.github.com> date: 2022-09-30T09:43:02+01:00 summary: gh-96348: Deprecate the 3-arg signature of coroutine.throw and generator.throw (GH-96428) files: A Misc/NEWS.d/next/Core and Builtins/2022-08-31-18-46-13.gh-issue-96348.xzCoTP.rst M Doc/reference/datamodel.rst M Doc/reference/expressions.rst M Doc/whatsnew/3.12.rst M Lib/contextlib.py M Lib/test/test_asyncgen.py M Lib/test/test_asyncio/test_futures.py M Lib/test/test_coroutines.py M Lib/test/test_generators.py M Lib/test/test_types.py M Misc/ACKS M Modules/_asynciomodule.c M Objects/genobject.c M Objects/iterobject.c diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 758f3aef3ee3..c93269ab04b6 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -2996,6 +2996,11 @@ generators, coroutines do not directly support iteration. above. If the exception is not caught in the coroutine, it propagates back to the caller. + .. versionchanged:: 3.12 + + The second signature \(type\[, value\[, traceback\]\]\) is deprecated and + may be removed in a future version of Python. + .. method:: coroutine.close() Causes the coroutine to clean itself up and exit. If the coroutine diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst index 6d23e473cdcd..a6ca55dafe53 100644 --- a/Doc/reference/expressions.rst +++ b/Doc/reference/expressions.rst @@ -582,6 +582,11 @@ is already executing raises a :exc:`ValueError` exception. :attr:`~BaseException.__traceback__` attribute stored in *value* may be cleared. + .. versionchanged:: 3.12 + + The second signature \(type\[, value\[, traceback\]\]\) is deprecated and + may be removed in a future version of Python. + .. index:: exception: GeneratorExit @@ -738,7 +743,8 @@ which are used to control the execution of a generator function. because there is no yield expression that could receive the value. -.. coroutinemethod:: agen.athrow(type[, value[, traceback]]) +.. coroutinemethod:: agen.athrow(value) + agen.athrow(type[, value[, traceback]]) Returns an awaitable that raises an exception of type ``type`` at the point where the asynchronous generator was paused, and returns the next value @@ -750,6 +756,11 @@ which are used to control the execution of a generator function. raises a different exception, then when the awaitable is run that exception propagates to the caller of the awaitable. + .. versionchanged:: 3.12 + + The second signature \(type\[, value\[, traceback\]\]\) is deprecated and + may be removed in a future version of Python. + .. index:: exception: GeneratorExit diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 2a31160decc1..e14a2bd0133b 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -182,6 +182,11 @@ Deprecated and tailor them to your needs. (Contributed by Erlend E. Aasland in :gh:`90016`.) +* The 3-arg signatures (type, value, traceback) of :meth:`~coroutine.throw`, + :meth:`~generator.throw` and :meth:`~agen.athrow` are deprecated and + may be removed in a future version of Python. Use the single-arg versions + of these functions instead. (Contributed by Ofey Chan in :gh:`89874`.) + Pending Removal in Python 3.13 ------------------------------ diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 625bb33b12d5..d5822219b3e2 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -152,7 +152,7 @@ def __exit__(self, typ, value, traceback): # tell if we get the same exception back value = typ() try: - self.gen.throw(typ, value, traceback) + self.gen.throw(value) except StopIteration as exc: # Suppress StopIteration *unless* it's the same exception that # was passed to throw(). This prevents a StopIteration @@ -219,7 +219,7 @@ async def __aexit__(self, typ, value, traceback): # tell if we get the same exception back value = typ() try: - await self.gen.athrow(typ, value, traceback) + await self.gen.athrow(value) except StopAsyncIteration as exc: # Suppress StopIteration *unless* it's the same exception that # was passed to throw(). This prevents a StopIteration diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index fb22f411c2e2..f6184c0cab46 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -2,6 +2,7 @@ import types import unittest import contextlib +import warnings from test.support.import_helper import import_module from test.support import gc_collect, requires_working_socket @@ -377,6 +378,13 @@ async def async_gen_wrapper(): self.compare_generators(sync_gen_wrapper(), async_gen_wrapper()) + def test_async_gen_3_arg_deprecation_warning(self): + async def gen(): + yield 123 + + with self.assertWarns(DeprecationWarning): + gen().athrow(GeneratorExit, GeneratorExit(), None) + def test_async_gen_api_01(self): async def gen(): yield 123 @@ -650,7 +658,7 @@ def test1(anext): agen = agenfn() with contextlib.closing(anext(agen, "default").__await__()) as g: self.assertEqual(g.send(None), 1) - self.assertEqual(g.throw(MyError, MyError(), None), 2) + self.assertEqual(g.throw(MyError()), 2) try: g.send(None) except StopIteration as e: @@ -663,9 +671,9 @@ def test2(anext): agen = agenfn() with contextlib.closing(anext(agen, "default").__await__()) as g: self.assertEqual(g.send(None), 1) - self.assertEqual(g.throw(MyError, MyError(), None), 2) + self.assertEqual(g.throw(MyError()), 2) with self.assertRaises(MyError): - g.throw(MyError, MyError(), None) + g.throw(MyError()) def test3(anext): agen = agenfn() @@ -692,9 +700,9 @@ async def agenfn(): agen = agenfn() with contextlib.closing(anext(agen, "default").__await__()) as g: self.assertEqual(g.send(None), 10) - self.assertEqual(g.throw(MyError, MyError(), None), 20) + self.assertEqual(g.throw(MyError()), 20) with self.assertRaisesRegex(MyError, 'val'): - g.throw(MyError, MyError('val'), None) + g.throw(MyError('val')) def test5(anext): @types.coroutine @@ -713,7 +721,7 @@ async def agenfn(): with contextlib.closing(anext(agen, "default").__await__()) as g: self.assertEqual(g.send(None), 10) with self.assertRaisesRegex(StopIteration, 'default'): - g.throw(MyError, MyError(), None) + g.throw(MyError()) def test6(anext): @types.coroutine @@ -728,7 +736,7 @@ async def agenfn(): agen = agenfn() with contextlib.closing(anext(agen, "default").__await__()) as g: with self.assertRaises(MyError): - g.throw(MyError, MyError(), None) + g.throw(MyError()) def run_test(test): with self.subTest('pure-Python anext()'): diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index f4a46ec90a16..11d427393080 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -10,6 +10,7 @@ from types import GenericAlias import asyncio from asyncio import futures +import warnings from test.test_asyncio import utils as test_utils from test import support @@ -619,10 +620,14 @@ def test_future_stop_iteration_args(self): def test_future_iter_throw(self): fut = self._new_future(loop=self.loop) fi = iter(fut) - self.assertRaises(TypeError, fi.throw, - Exception, Exception("elephant"), 32) - self.assertRaises(TypeError, fi.throw, - Exception("elephant"), Exception("elephant")) + with self.assertWarns(DeprecationWarning): + self.assertRaises(Exception, fi.throw, Exception, Exception("zebra"), None) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + self.assertRaises(TypeError, fi.throw, + Exception, Exception("elephant"), 32) + self.assertRaises(TypeError, fi.throw, + Exception("elephant"), Exception("elephant")) self.assertRaises(TypeError, fi.throw, list) def test_future_del_collect(self): diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py index 8fff2d47c10f..9a2279d353ff 100644 --- a/Lib/test/test_coroutines.py +++ b/Lib/test/test_coroutines.py @@ -709,9 +709,16 @@ async def foo(): aw = coro.__await__() next(aw) with self.assertRaises(ZeroDivisionError): - aw.throw(ZeroDivisionError, None, None) + aw.throw(ZeroDivisionError()) self.assertEqual(N, 102) + coro = foo() + aw = coro.__await__() + next(aw) + with self.assertRaises(ZeroDivisionError): + with self.assertWarns(DeprecationWarning): + aw.throw(ZeroDivisionError, ZeroDivisionError(), None) + def test_func_11(self): async def func(): pass coro = func() diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index e5aa7da1e0df..fb2d9ced0633 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -342,6 +342,15 @@ def generator(): with self.assertRaises(StopIteration): gen.throw(E) + def test_gen_3_arg_deprecation_warning(self): + def g(): + yield 42 + + gen = g() + with self.assertWarns(DeprecationWarning): + with self.assertRaises(TypeError): + gen.throw(TypeError, TypeError(24), None) + def test_stopiteration_error(self): # See also PEP 479. @@ -2113,6 +2122,12 @@ def printsolution(self, x):
g.throw(ValueError("xyz")) # value only caught ValueError (xyz)
+>>> import warnings +>>> warnings.filterwarnings("ignore", category=DeprecationWarning) + +# Filter DeprecationWarning: regarding the (type, val, tb) signature of throw(). +# Deprecation warnings are re-enabled below. +
g.throw(ValueError, ValueError(1)) # value+matching type caught ValueError (1)
@@ -2181,6 +2196,12 @@ def printsolution(self, x): ... ValueError: 7 +>>> warnings.filters.pop(0) +('ignore', None, <class 'DeprecationWarning'>, None, 0) + +# Re-enable DeprecationWarning: the (type, val, tb) exception representation is deprecated, +# and may be removed in a future version of Python. + Plain "raise" inside a generator should preserve the traceback (#13188). The traceback should have 3 levels: - g.throw() diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index f00da0a758d4..af095632a36f 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -2072,7 +2072,7 @@ def foo(): return gen wrapper = foo() wrapper.send(None) with self.assertRaisesRegex(Exception, 'ham'): - wrapper.throw(Exception, Exception('ham')) + wrapper.throw(Exception('ham')) # decorate foo second time foo = types.coroutine(foo) diff --git a/Misc/ACKS b/Misc/ACKS index 0edea9219d05..fc0e745e28ce 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -297,6 +297,7 @@ Michael Cetrulo Dave Chambers Pascal Chambon Nicholas Chammas +Ofey Chan John Chandler Hye-Shik Chang Jeffrey Chang diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-08-31-18-46-13.gh-issue-96348.xzCoTP.rst b/Misc/NEWS.d/next/Core and Builtins/2022-08-31-18-46-13.gh-issue-96348.xzCoTP.rst new file mode 100644 index 000000000000..5d3bd17b5786 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-08-31-18-46-13.gh-issue-96348.xzCoTP.rst @@ -0,0 +1,2 @@ +Emit a DeprecationWarning when :meth:`~generator.throw`, :meth:`~coroutine.throw` or :meth:`~agen.athrow` +are called with more than one argument. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 9d2f83bf6c73..5a5881b873e2 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1668,6 +1668,14 @@ FutureIter_throw(futureiterobject *self, PyObject *const *args, Py_ssize_t nargs if (!_PyArg_CheckPositional("throw", nargs, 1, 3)) { return NULL; } + if (nargs > 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "the (type, exc, tb) signature of throw() is deprecated, " + "use the single-arg signature instead.", + 1) < 0) { + return NULL; + } + } type = args[0]; if (nargs == 3) { diff --git a/Objects/genobject.c b/Objects/genobject.c index da4afecc69c8..ad4fbed6d8d5 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -418,7 +418,9 @@ PyDoc_STRVAR(throw_doc, throw(type[,value[,tb]])\n\ \n\ Raise exception in generator, return next yielded value or raise\n\ -StopIteration."); +StopIteration.\n\ +the (type, val, tb) signature is deprecated, \n\ +and may be removed in a future version of Python."); static PyObject * _gen_throw(PyGenObject *gen, int close_on_genexit, @@ -559,6 +561,14 @@ gen_throw(PyGenObject *gen, PyObject *const *args, Py_ssize_t nargs) if (!_PyArg_CheckPositional("throw", nargs, 1, 3)) { return NULL; } + if (nargs > 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "the (type, exc, tb) signature of throw() is deprecated, " + "use the single-arg signature instead.", + 1) < 0) { + return NULL; + } + } typ = args[0]; if (nargs == 3) { val = args[1]; @@ -1147,7 +1157,10 @@ PyDoc_STRVAR(coro_throw_doc, throw(type[,value[,traceback]])\n\ \n\ Raise exception in coroutine, return next iterated value or raise\n\ -StopIteration."); +StopIteration.\n\ +the (type, val, tb) signature is deprecated, \n\ +and may be removed in a future version of Python."); + PyDoc_STRVAR(coro_close_doc, "close() -> raise GeneratorExit inside coroutine."); @@ -1500,6 +1513,14 @@ async_gen_aclose(PyAsyncGenObject *o, PyObject *arg) static PyObject * async_gen_athrow(PyAsyncGenObject *o, PyObject *args) { + if (PyTuple_GET_SIZE(args) > 1) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "the (type, exc, tb) signature of athrow() is deprecated, " + "use the single-arg signature instead.", + 1) < 0) { + return NULL; + } + } if (async_gen_init_hooks(o)) { return NULL; } @@ -1537,7 +1558,12 @@ PyDoc_STRVAR(async_asend_doc, "asend(v) -> send 'v' in generator."); PyDoc_STRVAR(async_athrow_doc, -"athrow(typ[,val[,tb]]) -> raise exception in generator."); +"athrow(value)\n\ +athrow(type[,value[,tb]])\n\ +\n\ +raise exception in generator.\n\ +the (type, val, tb) signature is deprecated, \n\ +and may be removed in a future version of Python."); static PyMethodDef async_gen_methods[] = { {"asend", (PyCFunction)async_gen_asend, METH_O, async_asend_doc}, diff --git a/Objects/iterobject.c b/Objects/iterobject.c index 1732a037600c..62c36146d64f 100644 --- a/Objects/iterobject.c +++ b/Objects/iterobject.c @@ -428,8 +428,13 @@ return next yielded value or raise StopIteration."); PyDoc_STRVAR(throw_doc, -"throw(typ[,val[,tb]]) -> raise exception in the wrapped iterator,\n\ -return next yielded value or raise StopIteration."); +"throw(value)\n\ +throw(typ[,val[,tb]])\n\ +\n\ +raise exception in the wrapped iterator, return next yielded value\n\ +or raise StopIteration.\n\ +the (type, val, tb) signature is deprecated, \n\ +and may be removed in a future version of Python."); PyDoc_STRVAR(close_doc,
participants (1)
-
iritkatriel