From 037245c5ac46c3436f617a1f5d965929754be239 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Fri, 9 Oct 2020 17:15:15 -0700 Subject: [PATCH] bpo-41756: Add PyIter_Send function (#22443) --- Doc/c-api/gen.rst | 5 ---- Doc/c-api/iter.rst | 14 +++++++++++ Doc/data/refcounts.dat | 5 ++++ Doc/whatsnew/3.10.rst | 4 ++++ Include/abstract.h | 16 +++++++++++++ Include/genobject.h | 8 +------ .../2020-09-28-14-31-07.bpo-41756.ZZ5wJG.rst | 3 +++ Modules/_asynciomodule.c | 9 +------ Modules/_testcapimodule.c | 3 ++- Objects/abstract.c | 24 +++++++++++++++++++ Objects/genobject.c | 8 +------ Python/ceval.c | 21 ++++++---------- 12 files changed, 78 insertions(+), 42 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2020-09-28-14-31-07.bpo-41756.ZZ5wJG.rst diff --git a/Doc/c-api/gen.rst b/Doc/c-api/gen.rst index e098425e636..600f53486f7 100644 --- a/Doc/c-api/gen.rst +++ b/Doc/c-api/gen.rst @@ -15,11 +15,6 @@ than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`. The C structure used for generator objects. -.. c:type:: PySendResult - - The enum value used to represent different results of :c:func:`PyGen_Send`. - - .. c:var:: PyTypeObject PyGen_Type The type object corresponding to generator objects. diff --git a/Doc/c-api/iter.rst b/Doc/c-api/iter.rst index a2992b3452f..a068a43c86b 100644 --- a/Doc/c-api/iter.rst +++ b/Doc/c-api/iter.rst @@ -44,3 +44,17 @@ something like this:: else { /* continue doing useful work */ } + + +.. c:type:: PySendResult + + The enum value used to represent different results of :c:func:`PyIter_Send`. + + +.. c:function:: PySendResult PyIter_Send(PyObject *iter, PyObject *arg, PyObject **presult) + + Sends the *arg* value into the iterator *iter*. Returns: + + - ``PYGEN_RETURN`` if iterator returns. Return value is returned via *presult*. + - ``PYGEN_NEXT`` if iterator yields. Yielded value is returned via *presult*. + - ``PYGEN_ERROR`` if iterator has raised and exception. *presult* is set to ``NULL``. diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 6b1bde37967..87ce5d03d00 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -1081,6 +1081,11 @@ PyIter_Check:PyObject*:o:0: PyIter_Next:PyObject*::+1: PyIter_Next:PyObject*:o:0: +PyIter_Send:int::: +PyIter_Send:PyObject*:iter:0: +PyIter_Send:PyObject*:arg:0: +PyIter_Send:PyObject**:presult:+1: + PyList_Append:int::: PyList_Append:PyObject*:list:0: PyList_Append:PyObject*:item:+1: diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 7401ba722fb..1c50978a8b7 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -314,6 +314,10 @@ New Features search function. (Contributed by Hai Shi in :issue:`41842`.) +* The :c:func:`PyIter_Send` and :c:func:`PyGen_Send` functions were added to allow + sending value into iterator without raising ``StopIteration`` exception. + (Contributed by Vladimir Matveev in :issue:`41756`.) + Porting to Python 3.10 ---------------------- diff --git a/Include/abstract.h b/Include/abstract.h index a23b7dc78f4..716cd4b5ebb 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -338,6 +338,22 @@ PyAPI_FUNC(int) PyIter_Check(PyObject *); NULL with an exception means an error occurred. */ PyAPI_FUNC(PyObject *) PyIter_Next(PyObject *); +typedef enum { + PYGEN_RETURN = 0, + PYGEN_ERROR = -1, + PYGEN_NEXT = 1, +} PySendResult; + +/* Takes generator, coroutine or iterator object and sends the value into it. + Returns: + - PYGEN_RETURN (0) if generator has returned. + 'result' parameter is filled with return value + - PYGEN_ERROR (-1) if exception was raised. + 'result' parameter is NULL + - PYGEN_NEXT (1) if generator has yielded. + 'result' parameter is filled with yielded value. */ +PyAPI_FUNC(PySendResult) PyIter_Send(PyObject *, PyObject *, PyObject **); + /* === Number Protocol ================================================== */ diff --git a/Include/genobject.h b/Include/genobject.h index 7488054c68f..e719b25a800 100644 --- a/Include/genobject.h +++ b/Include/genobject.h @@ -9,6 +9,7 @@ extern "C" { #endif #include "pystate.h" /* _PyErr_StackItem */ +#include "abstract.h" /* PySendResult */ /* _PyGenObject_HEAD defines the initial segment of generator and coroutine objects. */ @@ -41,16 +42,9 @@ PyAPI_FUNC(PyObject *) PyGen_NewWithQualName(PyFrameObject *, PyObject *name, PyObject *qualname); PyAPI_FUNC(int) _PyGen_SetStopIterationValue(PyObject *); PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **); -PyAPI_FUNC(PyObject *) _PyGen_Send(PyGenObject *, PyObject *); PyObject *_PyGen_yf(PyGenObject *); PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self); -typedef enum { - PYGEN_RETURN = 0, - PYGEN_ERROR = -1, - PYGEN_NEXT = 1, -} PySendResult; - /* Sends the value into the generator or the coroutine. Returns: - PYGEN_RETURN (0) if generator has returned. 'result' parameter is filled with return value diff --git a/Misc/NEWS.d/next/C API/2020-09-28-14-31-07.bpo-41756.ZZ5wJG.rst b/Misc/NEWS.d/next/C API/2020-09-28-14-31-07.bpo-41756.ZZ5wJG.rst new file mode 100644 index 00000000000..f7e27b44015 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2020-09-28-14-31-07.bpo-41756.ZZ5wJG.rst @@ -0,0 +1,3 @@ +Add `PyIter_Send` function to allow sending value into +generator/coroutine/iterator without raising StopIteration exception to +signal return. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 2151f20281a..f01e5884c6f 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -16,7 +16,6 @@ _Py_IDENTIFIER(add_done_callback); _Py_IDENTIFIER(call_soon); _Py_IDENTIFIER(cancel); _Py_IDENTIFIER(get_event_loop); -_Py_IDENTIFIER(send); _Py_IDENTIFIER(throw); @@ -2695,13 +2694,7 @@ task_step_impl(TaskObj *task, PyObject *exc) int gen_status = PYGEN_ERROR; if (exc == NULL) { - if (PyGen_CheckExact(coro) || PyCoro_CheckExact(coro)) { - gen_status = PyGen_Send((PyGenObject*)coro, Py_None, &result); - } - else { - result = _PyObject_CallMethodIdOneArg(coro, &PyId_send, Py_None); - gen_status = gen_status_from_result(&result); - } + gen_status = PyIter_Send(coro, Py_None, &result); } else { result = _PyObject_CallMethodIdOneArg(coro, &PyId_throw, exc); diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 0e098779696..8c7544fa90e 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5028,6 +5028,7 @@ dict_get_version(PyObject *self, PyObject *args) static PyObject * raise_SIGINT_then_send_None(PyObject *self, PyObject *args) { + _Py_IDENTIFIER(send); PyGenObject *gen; if (!PyArg_ParseTuple(args, "O!", &PyGen_Type, &gen)) @@ -5044,7 +5045,7 @@ raise_SIGINT_then_send_None(PyObject *self, PyObject *args) because we check for signals before every bytecode operation. */ raise(SIGINT); - return _PyGen_Send(gen, Py_None); + return _PyObject_CallMethodIdOneArg((PyObject *)gen, &PyId_send, Py_None); } diff --git a/Objects/abstract.c b/Objects/abstract.c index 2ab3371a3f3..502a2d64e25 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2669,6 +2669,30 @@ PyIter_Next(PyObject *iter) return result; } +PySendResult +PyIter_Send(PyObject *iter, PyObject *arg, PyObject **result) +{ + _Py_IDENTIFIER(send); + assert(result != NULL); + + if (PyGen_CheckExact(iter) || PyCoro_CheckExact(iter)) { + return PyGen_Send((PyGenObject *)iter, arg, result); + } + + if (arg == Py_None && PyIter_Check(iter)) { + *result = Py_TYPE(iter)->tp_iternext(iter); + } + else { + *result = _PyObject_CallMethodIdOneArg(iter, &PyId_send, arg); + } + if (*result != NULL) { + return PYGEN_NEXT; + } + if (_PyGen_FetchStopIterationValue(result) == 0) { + return PYGEN_RETURN; + } + return PYGEN_ERROR; +} /* * Flatten a sequence of bytes() objects into a C array of diff --git a/Objects/genobject.c b/Objects/genobject.c index f0943ae847c..eb134ebf4bc 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -308,12 +308,6 @@ gen_send(PyGenObject *gen, PyObject *arg) return gen_send_ex(gen, arg, 0, 0); } -PyObject * -_PyGen_Send(PyGenObject *gen, PyObject *arg) -{ - return gen_send(gen, arg); -} - PyDoc_STRVAR(close_doc, "close() -> raise GeneratorExit inside generator."); @@ -1012,7 +1006,7 @@ PyDoc_STRVAR(coro_close_doc, "close() -> raise GeneratorExit inside coroutine."); static PyMethodDef coro_methods[] = { - {"send",(PyCFunction)_PyGen_Send, METH_O, coro_send_doc}, + {"send",(PyCFunction)gen_send, METH_O, coro_send_doc}, {"throw",(PyCFunction)gen_throw, METH_VARARGS, coro_throw_doc}, {"close",(PyCFunction)gen_close, METH_NOARGS, coro_close_doc}, {NULL, NULL} /* Sentinel */ diff --git a/Python/ceval.c b/Python/ceval.c index 500c588e3c2..762de577e6b 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2210,24 +2210,17 @@ main_loop: case TARGET(YIELD_FROM): { PyObject *v = POP(); PyObject *receiver = TOP(); - int is_gen_or_coro = PyGen_CheckExact(receiver) || PyCoro_CheckExact(receiver); - int gen_status; - if (tstate->c_tracefunc == NULL && is_gen_or_coro) { - gen_status = PyGen_Send((PyGenObject *)receiver, v, &retval); + PySendResult gen_status; + if (tstate->c_tracefunc == NULL) { + gen_status = PyIter_Send(receiver, v, &retval); } else { - if (is_gen_or_coro) { - retval = _PyGen_Send((PyGenObject *)receiver, v); + _Py_IDENTIFIER(send); + if (v == Py_None && PyIter_Check(receiver)) { + retval = Py_TYPE(receiver)->tp_iternext(receiver); } else { - _Py_IDENTIFIER(send); - if (v == Py_None) { - retval = Py_TYPE(receiver)->tp_iternext(receiver); - } - else { - retval = _PyObject_CallMethodIdOneArg(receiver, &PyId_send, v); - } + retval = _PyObject_CallMethodIdOneArg(receiver, &PyId_send, v); } - if (retval == NULL) { if (tstate->c_tracefunc != NULL && _PyErr_ExceptionMatches(tstate, PyExc_StopIteration))