Skip to content

Commit 83a3de4

Browse files
authored
gh-96348: Deprecate the 3-arg signature of coroutine.throw and generator.throw (GH-96428)
1 parent 9a11ed8 commit 83a3de4

14 files changed

+125
-21
lines changed

Doc/reference/datamodel.rst

+5
Original file line numberDiff line numberDiff line change
@@ -2996,6 +2996,11 @@ generators, coroutines do not directly support iteration.
29962996
above. If the exception is not caught in the coroutine, it propagates
29972997
back to the caller.
29982998

2999+
.. versionchanged:: 3.12
3000+
3001+
The second signature \(type\[, value\[, traceback\]\]\) is deprecated and
3002+
may be removed in a future version of Python.
3003+
29993004
.. method:: coroutine.close()
30003005

30013006
Causes the coroutine to clean itself up and exit. If the coroutine

Doc/reference/expressions.rst

+12-1
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,11 @@ is already executing raises a :exc:`ValueError` exception.
582582
:attr:`~BaseException.__traceback__` attribute stored in *value* may
583583
be cleared.
584584

585+
.. versionchanged:: 3.12
586+
587+
The second signature \(type\[, value\[, traceback\]\]\) is deprecated and
588+
may be removed in a future version of Python.
589+
585590
.. index:: exception: GeneratorExit
586591

587592

@@ -738,7 +743,8 @@ which are used to control the execution of a generator function.
738743
because there is no yield expression that could receive the value.
739744

740745

741-
.. coroutinemethod:: agen.athrow(type[, value[, traceback]])
746+
.. coroutinemethod:: agen.athrow(value)
747+
agen.athrow(type[, value[, traceback]])
742748

743749
Returns an awaitable that raises an exception of type ``type`` at the point
744750
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.
750756
raises a different exception, then when the awaitable is run that exception
751757
propagates to the caller of the awaitable.
752758

759+
.. versionchanged:: 3.12
760+
761+
The second signature \(type\[, value\[, traceback\]\]\) is deprecated and
762+
may be removed in a future version of Python.
763+
753764
.. index:: exception: GeneratorExit
754765

755766

Doc/whatsnew/3.12.rst

+5
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ Deprecated
182182
and tailor them to your needs.
183183
(Contributed by Erlend E. Aasland in :gh:`90016`.)
184184

185+
* The 3-arg signatures (type, value, traceback) of :meth:`~coroutine.throw`,
186+
:meth:`~generator.throw` and :meth:`~agen.athrow` are deprecated and
187+
may be removed in a future version of Python. Use the single-arg versions
188+
of these functions instead. (Contributed by Ofey Chan in :gh:`89874`.)
189+
185190

186191
Pending Removal in Python 3.13
187192
------------------------------

Lib/contextlib.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def __exit__(self, typ, value, traceback):
152152
# tell if we get the same exception back
153153
value = typ()
154154
try:
155-
self.gen.throw(typ, value, traceback)
155+
self.gen.throw(value)
156156
except StopIteration as exc:
157157
# Suppress StopIteration *unless* it's the same exception that
158158
# was passed to throw(). This prevents a StopIteration
@@ -219,7 +219,7 @@ async def __aexit__(self, typ, value, traceback):
219219
# tell if we get the same exception back
220220
value = typ()
221221
try:
222-
await self.gen.athrow(typ, value, traceback)
222+
await self.gen.athrow(value)
223223
except StopAsyncIteration as exc:
224224
# Suppress StopIteration *unless* it's the same exception that
225225
# was passed to throw(). This prevents a StopIteration

Lib/test/test_asyncgen.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import types
33
import unittest
44
import contextlib
5+
import warnings
56

67
from test.support.import_helper import import_module
78
from test.support import gc_collect, requires_working_socket
@@ -377,6 +378,13 @@ async def async_gen_wrapper():
377378

378379
self.compare_generators(sync_gen_wrapper(), async_gen_wrapper())
379380

381+
def test_async_gen_3_arg_deprecation_warning(self):
382+
async def gen():
383+
yield 123
384+
385+
with self.assertWarns(DeprecationWarning):
386+
gen().athrow(GeneratorExit, GeneratorExit(), None)
387+
380388
def test_async_gen_api_01(self):
381389
async def gen():
382390
yield 123
@@ -650,7 +658,7 @@ def test1(anext):
650658
agen = agenfn()
651659
with contextlib.closing(anext(agen, "default").__await__()) as g:
652660
self.assertEqual(g.send(None), 1)
653-
self.assertEqual(g.throw(MyError, MyError(), None), 2)
661+
self.assertEqual(g.throw(MyError()), 2)
654662
try:
655663
g.send(None)
656664
except StopIteration as e:
@@ -663,9 +671,9 @@ def test2(anext):
663671
agen = agenfn()
664672
with contextlib.closing(anext(agen, "default").__await__()) as g:
665673
self.assertEqual(g.send(None), 1)
666-
self.assertEqual(g.throw(MyError, MyError(), None), 2)
674+
self.assertEqual(g.throw(MyError()), 2)
667675
with self.assertRaises(MyError):
668-
g.throw(MyError, MyError(), None)
676+
g.throw(MyError())
669677

670678
def test3(anext):
671679
agen = agenfn()
@@ -692,9 +700,9 @@ async def agenfn():
692700
agen = agenfn()
693701
with contextlib.closing(anext(agen, "default").__await__()) as g:
694702
self.assertEqual(g.send(None), 10)
695-
self.assertEqual(g.throw(MyError, MyError(), None), 20)
703+
self.assertEqual(g.throw(MyError()), 20)
696704
with self.assertRaisesRegex(MyError, 'val'):
697-
g.throw(MyError, MyError('val'), None)
705+
g.throw(MyError('val'))
698706

699707
def test5(anext):
700708
@types.coroutine
@@ -713,7 +721,7 @@ async def agenfn():
713721
with contextlib.closing(anext(agen, "default").__await__()) as g:
714722
self.assertEqual(g.send(None), 10)
715723
with self.assertRaisesRegex(StopIteration, 'default'):
716-
g.throw(MyError, MyError(), None)
724+
g.throw(MyError())
717725

718726
def test6(anext):
719727
@types.coroutine
@@ -728,7 +736,7 @@ async def agenfn():
728736
agen = agenfn()
729737
with contextlib.closing(anext(agen, "default").__await__()) as g:
730738
with self.assertRaises(MyError):
731-
g.throw(MyError, MyError(), None)
739+
g.throw(MyError())
732740

733741
def run_test(test):
734742
with self.subTest('pure-Python anext()'):

Lib/test/test_asyncio/test_futures.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from types import GenericAlias
1111
import asyncio
1212
from asyncio import futures
13+
import warnings
1314
from test.test_asyncio import utils as test_utils
1415
from test import support
1516

@@ -619,10 +620,14 @@ def test_future_stop_iteration_args(self):
619620
def test_future_iter_throw(self):
620621
fut = self._new_future(loop=self.loop)
621622
fi = iter(fut)
622-
self.assertRaises(TypeError, fi.throw,
623-
Exception, Exception("elephant"), 32)
624-
self.assertRaises(TypeError, fi.throw,
625-
Exception("elephant"), Exception("elephant"))
623+
with self.assertWarns(DeprecationWarning):
624+
self.assertRaises(Exception, fi.throw, Exception, Exception("zebra"), None)
625+
with warnings.catch_warnings():
626+
warnings.filterwarnings("ignore", category=DeprecationWarning)
627+
self.assertRaises(TypeError, fi.throw,
628+
Exception, Exception("elephant"), 32)
629+
self.assertRaises(TypeError, fi.throw,
630+
Exception("elephant"), Exception("elephant"))
626631
self.assertRaises(TypeError, fi.throw, list)
627632

628633
def test_future_del_collect(self):

Lib/test/test_coroutines.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -709,9 +709,16 @@ async def foo():
709709
aw = coro.__await__()
710710
next(aw)
711711
with self.assertRaises(ZeroDivisionError):
712-
aw.throw(ZeroDivisionError, None, None)
712+
aw.throw(ZeroDivisionError())
713713
self.assertEqual(N, 102)
714714

715+
coro = foo()
716+
aw = coro.__await__()
717+
next(aw)
718+
with self.assertRaises(ZeroDivisionError):
719+
with self.assertWarns(DeprecationWarning):
720+
aw.throw(ZeroDivisionError, ZeroDivisionError(), None)
721+
715722
def test_func_11(self):
716723
async def func(): pass
717724
coro = func()

Lib/test/test_generators.py

+21
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,15 @@ def generator():
342342
with self.assertRaises(StopIteration):
343343
gen.throw(E)
344344

345+
def test_gen_3_arg_deprecation_warning(self):
346+
def g():
347+
yield 42
348+
349+
gen = g()
350+
with self.assertWarns(DeprecationWarning):
351+
with self.assertRaises(TypeError):
352+
gen.throw(TypeError, TypeError(24), None)
353+
345354
def test_stopiteration_error(self):
346355
# See also PEP 479.
347356

@@ -2113,6 +2122,12 @@ def printsolution(self, x):
21132122
>>> g.throw(ValueError("xyz")) # value only
21142123
caught ValueError (xyz)
21152124
2125+
>>> import warnings
2126+
>>> warnings.filterwarnings("ignore", category=DeprecationWarning)
2127+
2128+
# Filter DeprecationWarning: regarding the (type, val, tb) signature of throw().
2129+
# Deprecation warnings are re-enabled below.
2130+
21162131
>>> g.throw(ValueError, ValueError(1)) # value+matching type
21172132
caught ValueError (1)
21182133
@@ -2181,6 +2196,12 @@ def printsolution(self, x):
21812196
...
21822197
ValueError: 7
21832198
2199+
>>> warnings.filters.pop(0)
2200+
('ignore', None, <class 'DeprecationWarning'>, None, 0)
2201+
2202+
# Re-enable DeprecationWarning: the (type, val, tb) exception representation is deprecated,
2203+
# and may be removed in a future version of Python.
2204+
21842205
Plain "raise" inside a generator should preserve the traceback (#13188).
21852206
The traceback should have 3 levels:
21862207
- g.throw()

Lib/test/test_types.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2072,7 +2072,7 @@ def foo(): return gen
20722072
wrapper = foo()
20732073
wrapper.send(None)
20742074
with self.assertRaisesRegex(Exception, 'ham'):
2075-
wrapper.throw(Exception, Exception('ham'))
2075+
wrapper.throw(Exception('ham'))
20762076

20772077
# decorate foo second time
20782078
foo = types.coroutine(foo)

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ Michael Cetrulo
297297
Dave Chambers
298298
Pascal Chambon
299299
Nicholas Chammas
300+
Ofey Chan
300301
John Chandler
301302
Hye-Shik Chang
302303
Jeffrey Chang
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Emit a DeprecationWarning when :meth:`~generator.throw`, :meth:`~coroutine.throw` or :meth:`~agen.athrow`
2+
are called with more than one argument.

Modules/_asynciomodule.c

+8
Original file line numberDiff line numberDiff line change
@@ -1668,6 +1668,14 @@ FutureIter_throw(futureiterobject *self, PyObject *const *args, Py_ssize_t nargs
16681668
if (!_PyArg_CheckPositional("throw", nargs, 1, 3)) {
16691669
return NULL;
16701670
}
1671+
if (nargs > 1) {
1672+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1673+
"the (type, exc, tb) signature of throw() is deprecated, "
1674+
"use the single-arg signature instead.",
1675+
1) < 0) {
1676+
return NULL;
1677+
}
1678+
}
16711679

16721680
type = args[0];
16731681
if (nargs == 3) {

Objects/genobject.c

+29-3
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,9 @@ PyDoc_STRVAR(throw_doc,
418418
throw(type[,value[,tb]])\n\
419419
\n\
420420
Raise exception in generator, return next yielded value or raise\n\
421-
StopIteration.");
421+
StopIteration.\n\
422+
the (type, val, tb) signature is deprecated, \n\
423+
and may be removed in a future version of Python.");
422424

423425
static PyObject *
424426
_gen_throw(PyGenObject *gen, int close_on_genexit,
@@ -559,6 +561,14 @@ gen_throw(PyGenObject *gen, PyObject *const *args, Py_ssize_t nargs)
559561
if (!_PyArg_CheckPositional("throw", nargs, 1, 3)) {
560562
return NULL;
561563
}
564+
if (nargs > 1) {
565+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
566+
"the (type, exc, tb) signature of throw() is deprecated, "
567+
"use the single-arg signature instead.",
568+
1) < 0) {
569+
return NULL;
570+
}
571+
}
562572
typ = args[0];
563573
if (nargs == 3) {
564574
val = args[1];
@@ -1147,7 +1157,10 @@ PyDoc_STRVAR(coro_throw_doc,
11471157
throw(type[,value[,traceback]])\n\
11481158
\n\
11491159
Raise exception in coroutine, return next iterated value or raise\n\
1150-
StopIteration.");
1160+
StopIteration.\n\
1161+
the (type, val, tb) signature is deprecated, \n\
1162+
and may be removed in a future version of Python.");
1163+
11511164

11521165
PyDoc_STRVAR(coro_close_doc,
11531166
"close() -> raise GeneratorExit inside coroutine.");
@@ -1500,6 +1513,14 @@ async_gen_aclose(PyAsyncGenObject *o, PyObject *arg)
15001513
static PyObject *
15011514
async_gen_athrow(PyAsyncGenObject *o, PyObject *args)
15021515
{
1516+
if (PyTuple_GET_SIZE(args) > 1) {
1517+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1518+
"the (type, exc, tb) signature of athrow() is deprecated, "
1519+
"use the single-arg signature instead.",
1520+
1) < 0) {
1521+
return NULL;
1522+
}
1523+
}
15031524
if (async_gen_init_hooks(o)) {
15041525
return NULL;
15051526
}
@@ -1537,7 +1558,12 @@ PyDoc_STRVAR(async_asend_doc,
15371558
"asend(v) -> send 'v' in generator.");
15381559

15391560
PyDoc_STRVAR(async_athrow_doc,
1540-
"athrow(typ[,val[,tb]]) -> raise exception in generator.");
1561+
"athrow(value)\n\
1562+
athrow(type[,value[,tb]])\n\
1563+
\n\
1564+
raise exception in generator.\n\
1565+
the (type, val, tb) signature is deprecated, \n\
1566+
and may be removed in a future version of Python.");
15411567

15421568
static PyMethodDef async_gen_methods[] = {
15431569
{"asend", (PyCFunction)async_gen_asend, METH_O, async_asend_doc},

Objects/iterobject.c

+7-2
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,13 @@ return next yielded value or raise StopIteration.");
428428

429429

430430
PyDoc_STRVAR(throw_doc,
431-
"throw(typ[,val[,tb]]) -> raise exception in the wrapped iterator,\n\
432-
return next yielded value or raise StopIteration.");
431+
"throw(value)\n\
432+
throw(typ[,val[,tb]])\n\
433+
\n\
434+
raise exception in the wrapped iterator, return next yielded value\n\
435+
or raise StopIteration.\n\
436+
the (type, val, tb) signature is deprecated, \n\
437+
and may be removed in a future version of Python.");
433438

434439

435440
PyDoc_STRVAR(close_doc,

0 commit comments

Comments
 (0)