summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/gen.rst15
-rw-r--r--Doc/data/refcounts.dat5
-rw-r--r--Include/genobject.h15
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2020-09-12-12-55-45.bpo-41756.1h0tbV.rst2
-rw-r--r--Modules/_asynciomodule.c27
-rw-r--r--Objects/genobject.c65
-rw-r--r--Python/ceval.c58
7 files changed, 148 insertions, 39 deletions
diff --git a/Doc/c-api/gen.rst b/Doc/c-api/gen.rst
index 7441092..e098425 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 355a4d6..6b1bde3 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 a76dc92..7488054 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 0000000..b387cfd
--- /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 4a1c91e..2151f20 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 809838a..24aca98 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 f747faa..3de372f 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));