diff --git a/Doc/c-api/gen.rst b/Doc/c-api/gen.rst index 74410927bfd..e098425e636 100644 --- a/Doc/c-api/gen.rst +++ b/Doc/c-api/gen.rst @@ -15,6 +15,11 @@ 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. @@ -42,3 +47,13 @@ than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`. with ``__name__`` and ``__qualname__`` set to *name* and *qualname*. A reference to *frame* is stolen by this function. The *frame* argument must not be ``NULL``. + +.. c:function:: PySendResult PyGen_Send(PyGenObject *gen, PyObject *arg, PyObject **presult) + + Sends the *arg* value into the generator *gen*. Coroutine objects + are also allowed to be as the *gen* argument but they need to be + explicitly casted to PyGenObject*. Returns: + + - ``PYGEN_RETURN`` if generator returns. Return value is returned via *presult*. + - ``PYGEN_NEXT`` if generator yields. Yielded value is returned via *presult*. + - ``PYGEN_ERROR`` if generator has raised and exception. *presult* is set to ``NULL``. diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 355a4d6d3fa..6b1bde37967 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -959,6 +959,11 @@ PyGen_NewWithQualName:PyFrameObject*:frame:0: PyGen_NewWithQualName:PyObject*:name:0: PyGen_NewWithQualName:PyObject*:qualname:0: +PyGen_Send:int::: +PyGen_Send:PyGenObject*:gen:0: +PyGen_Send:PyObject*:arg:0: +PyGen_Send:PyObject**:presult:+1: + PyCoro_CheckExact:int::: PyCoro_CheckExact:PyObject*:ob:0: diff --git a/Include/genobject.h b/Include/genobject.h index a76dc92e811..7488054c68f 100644 --- a/Include/genobject.h +++ b/Include/genobject.h @@ -45,6 +45,21 @@ 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 + - 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) PyGen_Send(PyGenObject *, PyObject *, PyObject **); + #ifndef Py_LIMITED_API typedef struct { _PyGenObject_HEAD(cr) diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-09-12-12-55-45.bpo-41756.1h0tbV.rst b/Misc/NEWS.d/next/Core and Builtins/2020-09-12-12-55-45.bpo-41756.1h0tbV.rst new file mode 100644 index 00000000000..b387cfd9403 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-09-12-12-55-45.bpo-41756.1h0tbV.rst @@ -0,0 +1,2 @@ +Add PyGen_Send function to allow sending value into generator/coroutine +without raising StopIteration exception to signal return diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 4a1c91e9edd..2151f20281a 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2621,6 +2621,20 @@ task_set_error_soon(TaskObj *task, PyObject *et, const char *format, ...) Py_RETURN_NONE; } +static inline int +gen_status_from_result(PyObject **result) +{ + if (*result != NULL) { + return PYGEN_NEXT; + } + if (_PyGen_FetchStopIterationValue(result) == 0) { + return PYGEN_RETURN; + } + + assert(PyErr_Occurred()); + return PYGEN_ERROR; +} + static PyObject * task_step_impl(TaskObj *task, PyObject *exc) { @@ -2679,26 +2693,29 @@ task_step_impl(TaskObj *task, PyObject *exc) return NULL; } + int gen_status = PYGEN_ERROR; if (exc == NULL) { if (PyGen_CheckExact(coro) || PyCoro_CheckExact(coro)) { - result = _PyGen_Send((PyGenObject*)coro, Py_None); + 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); } } else { result = _PyObject_CallMethodIdOneArg(coro, &PyId_throw, exc); + gen_status = gen_status_from_result(&result); if (clear_exc) { /* We created 'exc' during this call */ Py_DECREF(exc); } } - if (result == NULL) { + if (gen_status == PYGEN_RETURN || gen_status == PYGEN_ERROR) { PyObject *et, *ev, *tb; - if (_PyGen_FetchStopIterationValue(&o) == 0) { + if (result != NULL) { /* The error is StopIteration and that means that the underlying coroutine has resolved */ @@ -2709,10 +2726,10 @@ task_step_impl(TaskObj *task, PyObject *exc) res = future_cancel((FutureObj*)task, task->task_cancel_msg); } else { - res = future_set_result((FutureObj*)task, o); + res = future_set_result((FutureObj*)task, result); } - Py_DECREF(o); + Py_DECREF(result); if (res == NULL) { return NULL; diff --git a/Objects/genobject.c b/Objects/genobject.c index 809838a4cd2..24aca988354 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -137,7 +137,7 @@ gen_dealloc(PyGenObject *gen) } static PyObject * -gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) +gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing, int *is_return_value) { PyThreadState *tstate = _PyThreadState_GET(); PyFrameObject *f = gen->gi_frame; @@ -170,6 +170,10 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) PyErr_SetNone(PyExc_StopAsyncIteration); } else { + if (is_return_value != NULL) { + *is_return_value = 1; + Py_RETURN_NONE; + } PyErr_SetNone(PyExc_StopIteration); } } @@ -230,18 +234,33 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) /* Delay exception instantiation if we can */ if (PyAsyncGen_CheckExact(gen)) { PyErr_SetNone(PyExc_StopAsyncIteration); + Py_CLEAR(result); } else if (arg) { - /* Set exception if not called by gen_iternext() */ - PyErr_SetNone(PyExc_StopIteration); + if (is_return_value != NULL) { + *is_return_value = 1; + } + else { + /* Set exception if not called by gen_iternext() */ + PyErr_SetNone(PyExc_StopIteration); + Py_CLEAR(result); + } + } + else { + Py_CLEAR(result); } } else { /* Async generators cannot return anything but None */ assert(!PyAsyncGen_CheckExact(gen)); - _PyGen_SetStopIterationValue(result); + if (is_return_value != NULL) { + *is_return_value = 1; + } + else { + _PyGen_SetStopIterationValue(result); + Py_CLEAR(result); + } } - Py_CLEAR(result); } else if (!result && PyErr_ExceptionMatches(PyExc_StopIteration)) { const char *msg = "generator raised StopIteration"; @@ -264,7 +283,7 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) _PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg); } - if (!result || _PyFrameHasCompleted(f)) { + if ((is_return_value && *is_return_value) || !result || _PyFrameHasCompleted(f)) { /* generator can't be rerun, so release the frame */ /* first clean reference cycle through stored exception traceback */ _PyErr_ClearExcState(&gen->gi_exc_state); @@ -283,7 +302,19 @@ return next yielded value or raise StopIteration."); PyObject * _PyGen_Send(PyGenObject *gen, PyObject *arg) { - return gen_send_ex(gen, arg, 0, 0); + return gen_send_ex(gen, arg, 0, 0, NULL); +} + +PySendResult +PyGen_Send(PyGenObject *gen, PyObject *arg, PyObject **result) +{ + assert(result != NULL); + + int is_return_value = 0; + if ((*result = gen_send_ex(gen, arg, 0, 0, &is_return_value)) == NULL) { + return PYGEN_ERROR; + } + return is_return_value ? PYGEN_RETURN : PYGEN_NEXT; } PyDoc_STRVAR(close_doc, @@ -365,7 +396,7 @@ gen_close(PyGenObject *gen, PyObject *args) } if (err == 0) PyErr_SetNone(PyExc_GeneratorExit); - retval = gen_send_ex(gen, Py_None, 1, 1); + retval = gen_send_ex(gen, Py_None, 1, 1, NULL); if (retval) { const char *msg = "generator ignored GeneratorExit"; if (PyCoro_CheckExact(gen)) { @@ -413,7 +444,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit, gen->gi_frame->f_state = state; Py_DECREF(yf); if (err < 0) - return gen_send_ex(gen, Py_None, 1, 0); + return gen_send_ex(gen, Py_None, 1, 0, NULL); goto throw_here; } if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) { @@ -465,10 +496,10 @@ _gen_throw(PyGenObject *gen, int close_on_genexit, assert(gen->gi_frame->f_lasti >= 0); gen->gi_frame->f_lasti += sizeof(_Py_CODEUNIT); if (_PyGen_FetchStopIterationValue(&val) == 0) { - ret = gen_send_ex(gen, val, 0, 0); + ret = gen_send_ex(gen, val, 0, 0, NULL); Py_DECREF(val); } else { - ret = gen_send_ex(gen, Py_None, 1, 0); + ret = gen_send_ex(gen, Py_None, 1, 0, NULL); } } return ret; @@ -522,7 +553,7 @@ throw_here: } PyErr_Restore(typ, val, tb); - return gen_send_ex(gen, Py_None, 1, 0); + return gen_send_ex(gen, Py_None, 1, 0, NULL); failed_throw: /* Didn't use our arguments, so restore their original refcounts */ @@ -551,7 +582,7 @@ gen_throw(PyGenObject *gen, PyObject *args) static PyObject * gen_iternext(PyGenObject *gen) { - return gen_send_ex(gen, NULL, 0, 0); + return gen_send_ex(gen, NULL, 0, 0, NULL); } /* @@ -1051,13 +1082,13 @@ coro_wrapper_dealloc(PyCoroWrapper *cw) static PyObject * coro_wrapper_iternext(PyCoroWrapper *cw) { - return gen_send_ex((PyGenObject *)cw->cw_coroutine, NULL, 0, 0); + return gen_send_ex((PyGenObject *)cw->cw_coroutine, NULL, 0, 0, NULL); } static PyObject * coro_wrapper_send(PyCoroWrapper *cw, PyObject *arg) { - return gen_send_ex((PyGenObject *)cw->cw_coroutine, arg, 0, 0); + return gen_send_ex((PyGenObject *)cw->cw_coroutine, arg, 0, 0, NULL); } static PyObject * @@ -1570,7 +1601,7 @@ async_gen_asend_send(PyAsyncGenASend *o, PyObject *arg) } o->ags_gen->ag_running_async = 1; - result = gen_send_ex((PyGenObject*)o->ags_gen, arg, 0, 0); + result = gen_send_ex((PyGenObject*)o->ags_gen, arg, 0, 0, NULL); result = async_gen_unwrap_value(o->ags_gen, result); if (result == NULL) { @@ -1926,7 +1957,7 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg) assert(o->agt_state == AWAITABLE_STATE_ITER); - retval = gen_send_ex((PyGenObject *)gen, arg, 0, 0); + retval = gen_send_ex((PyGenObject *)gen, arg, 0, 0, NULL); if (o->agt_args) { return async_gen_unwrap_value(o->agt_gen, retval); } else { diff --git a/Python/ceval.c b/Python/ceval.c index f747faaebf0..3de372f45a2 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2223,29 +2223,53 @@ main_loop: case TARGET(YIELD_FROM): { PyObject *v = POP(); PyObject *receiver = TOP(); - int err; - if (PyGen_CheckExact(receiver) || PyCoro_CheckExact(receiver)) { - retval = _PyGen_Send((PyGenObject *)receiver, v); + 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); } else { - _Py_IDENTIFIER(send); - if (v == Py_None) - retval = Py_TYPE(receiver)->tp_iternext(receiver); - else - retval = _PyObject_CallMethodIdOneArg(receiver, &PyId_send, v); + if (is_gen_or_coro) { + retval = _PyGen_Send((PyGenObject *)receiver, v); + } + else { + _Py_IDENTIFIER(send); + if (v == Py_None) { + retval = Py_TYPE(receiver)->tp_iternext(receiver); + } + else { + retval = _PyObject_CallMethodIdOneArg(receiver, &PyId_send, v); + } + } + + if (retval == NULL) { + if (tstate->c_tracefunc != NULL + && _PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) + call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f); + if (_PyGen_FetchStopIterationValue(&retval) == 0) { + gen_status = PYGEN_RETURN; + } + else { + gen_status = PYGEN_ERROR; + } + } + else { + gen_status = PYGEN_NEXT; + } } Py_DECREF(v); - if (retval == NULL) { - PyObject *val; - if (tstate->c_tracefunc != NULL - && _PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) - call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f); - err = _PyGen_FetchStopIterationValue(&val); - if (err < 0) - goto error; + if (gen_status == PYGEN_ERROR) { + assert (retval == NULL); + goto error; + } + if (gen_status == PYGEN_RETURN) { + assert (retval != NULL); + Py_DECREF(receiver); - SET_TOP(val); + SET_TOP(retval); + retval = NULL; DISPATCH(); } + assert (gen_status == PYGEN_NEXT); /* receiver remains on stack, retval is value to be yielded */ /* and repeat... */ assert(f->f_lasti >= (int)sizeof(_Py_CODEUNIT));