]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-41756: Add PyIter_Send function (#22443)
authorVladimir Matveev <vladima@fb.com>
Sat, 10 Oct 2020 00:15:15 +0000 (17:15 -0700)
committerGitHub <noreply@github.com>
Sat, 10 Oct 2020 00:15:15 +0000 (17:15 -0700)
12 files changed:
Doc/c-api/gen.rst
Doc/c-api/iter.rst
Doc/data/refcounts.dat
Doc/whatsnew/3.10.rst
Include/abstract.h
Include/genobject.h
Misc/NEWS.d/next/C API/2020-09-28-14-31-07.bpo-41756.ZZ5wJG.rst [new file with mode: 0644]
Modules/_asynciomodule.c
Modules/_testcapimodule.c
Objects/abstract.c
Objects/genobject.c
Python/ceval.c

index e098425e6364d9ef1430939d2f6dd77b68bb7f02..600f53486f79d5b187289931222691475b0ba325 100644 (file)
@@ -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.
index a2992b3452f91c3c1fb9d3fc9a5ebd4a75e3be3d..a068a43c86b6c39dd6614b4d32adc80a3a494448 100644 (file)
@@ -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``.
index 6b1bde37967ae9390e80784eef3d11a620b78bf9..87ce5d03d0064437c81422edb9d78ec6bfec9330 100644 (file)
@@ -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:
index 7401ba722fb4f98f71d6d6c70af85e000ac9fdd8..1c50978a8b7501d0b086c18b600cea55573bc082 100644 (file)
@@ -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
 ----------------------
 
index a23b7dc78f480d787aaba7463d2efabab3132a85..716cd4b5ebbba3e062ff1912da26cbaff72516c5 100644 (file)
@@ -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 ================================================== */
 
index 7488054c68fcd8bf8e3678265c6e957e5d58129b..e719b25a8007294b999def4c2d3f0959433dc163 100644 (file)
@@ -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 (file)
index 0000000..f7e27b4
--- /dev/null
@@ -0,0 +1,3 @@
+Add `PyIter_Send` function to allow sending value into
+generator/coroutine/iterator without raising StopIteration exception to
+signal return.
index 2151f20281a31bc61abc8c2b1d39bb42bf775d97..f01e5884c6fe2012a2eb0fb3d0fca5fbc62c33aa 100644 (file)
@@ -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);
index 0e098779696b737dacc27615f23c6a0020347978..8c7544fa90e280b7c2057fc69d5893550edc1b7c 100644 (file)
@@ -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);
 }
 
 
index 2ab3371a3f3cb9a1da6ca6c494f5e1d9b8e188fb..502a2d64e25e112814245ea81571e0e1cf54731a 100644 (file)
@@ -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
index f0943ae847c5438b487c7fc5a69eee6e8796a629..eb134ebf4bc8785173365d1728e8fa64b92a41f7 100644 (file)
@@ -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 */
index 500c588e3c2afb733380ef2983f4da6447ce58b0..762de577e6b550521e438af265b5799367aefcde 100644 (file)
@@ -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))