Skip to content

Commit

Permalink
pythongh-96348: Deprecate the 3-arg signature of coroutine.throw and …
Browse files Browse the repository at this point in the history
…generator.throw (pythonGH-96428)
  • Loading branch information
ofey404 authored and serhiy-storchaka committed Oct 2, 2022
1 parent 679cf96 commit 3c84af2
Show file tree
Hide file tree
Showing 14 changed files with 125 additions and 21 deletions.
5 changes: 5 additions & 0 deletions Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion Doc/reference/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand All @@ -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


Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,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
------------------------------
Expand Down
4 changes: 2 additions & 2 deletions Lib/contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
22 changes: 15 additions & 7 deletions Lib/test/test_asyncgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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()'):
Expand Down
13 changes: 9 additions & 4 deletions Lib/test/test_asyncio/test_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand Down
9 changes: 8 additions & 1 deletion Lib/test/test_coroutines.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ Michael Cetrulo
Dave Chambers
Pascal Chambon
Nicholas Chammas
Ofey Chan
John Chandler
Hye-Shik Chang
Jeffrey Chang
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
32 changes: 29 additions & 3 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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.");
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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},
Expand Down
9 changes: 7 additions & 2 deletions Objects/iterobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 3c84af2

Please sign in to comment.