[Python-checkins] bpo-41756: Add PyIter_Send function (#22443)
Vladimir Matveev
webhook-mailer at python.org
Fri Oct 9 20:15:24 EDT 2020
https://github.com/python/cpython/commit/037245c5ac46c3436f617a1f5d965929754be239
commit: 037245c5ac46c3436f617a1f5d965929754be239
branch: master
author: Vladimir Matveev <vladima at fb.com>
committer: GitHub <noreply at github.com>
date: 2020-10-09T17:15:15-07:00
summary:
bpo-41756: Add PyIter_Send function (#22443)
files:
A Misc/NEWS.d/next/C API/2020-09-28-14-31-07.bpo-41756.ZZ5wJG.rst
M Doc/c-api/gen.rst
M Doc/c-api/iter.rst
M Doc/data/refcounts.dat
M Doc/whatsnew/3.10.rst
M Include/abstract.h
M Include/genobject.h
M Modules/_asynciomodule.c
M Modules/_testcapimodule.c
M Objects/abstract.c
M Objects/genobject.c
M Python/ceval.c
diff --git a/Doc/c-api/gen.rst b/Doc/c-api/gen.rst
index e098425e6364d..600f53486f79d 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 a2992b3452f91..a068a43c86b6c 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 6b1bde37967ae..87ce5d03d0064 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 7401ba722fb4f..1c50978a8b750 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 a23b7dc78f480..716cd4b5ebbba 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 7488054c68fcd..e719b25a80072 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 0000000000000..f7e27b4401529
--- /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 2151f20281a31..f01e5884c6fe2 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 0e098779696b7..8c7544fa90e28 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 2ab3371a3f3cb..502a2d64e25e1 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 f0943ae847c54..eb134ebf4bc87 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 500c588e3c2af..762de577e6b55 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2210,24 +2210,17 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
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))
More information about the Python-checkins
mailing list