8000 bpo-41756: Add PyIter_Send function (#22443) · python/cpython@037245c · GitHub
[go: up one dir, main page]

Skip to content

Commit 037245c

Browse files
authored
bpo-41756: Add PyIter_Send function (#22443)
1 parent 9975cc5 commit 037245c

File tree

12 files changed

+78
-42
lines changed

12 files changed

+78
-42
lines changed

Doc/c-api/gen.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@ than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`.
1515
The C structure used for generator objects.
1616

1717

18-
.. c:type:: PySendResult
19-
20-
The enum value used to represent different results of :c:func:`PyGen_Send`.
21-
22-
2318
.. c:var:: PyTypeObject PyGen_Type
2419
2520
The type object corresponding to generator objects.

Doc/c-api/iter.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,17 @@ something like this::
4444
else {
4545
/* continue doing useful work */
4646
}
47+
48+
49+
.. c:type:: PySendResult
50+
51+
The enum value used to represent different results of :c:func:`PyIter_Send`.
52+
53+
54+
.. c:function:: PySendResult PyIter_Send(PyObject *iter, PyObject *arg, PyObject **presult)
55+
56+
Sends the *arg* value into the iterator *iter*. Returns:
57+
58+
- ``PYGEN_RETURN`` if iterator returns. Return value is returned via *presult*.
59+
- ``PYGEN_NEXT`` if iterator yields. Yielded value is returned via *presult*.
60+
- ``PYGEN_ERROR`` if iterator has raised and exception. *presult* is set to ``NULL``.

Doc/data/refcounts.dat

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,11 @@ PyIter_Check:PyObject*:o:0:
10811081
PyIter_Next:PyObject*::+1:
10821082
PyIter_Next:PyObject*:o:0:
10831083

1084+
PyIter_Send:int:::
1085+
PyIter_Send:PyObject*:iter:0:
1086+
PyIter_Send:PyObject*:arg:0:
1087+
PyIter_Send:PyObject**:presult:+1:
1088+
10841089
PyList_Append:int:::
10851090
PyList_Append:PyObject*:list:0:
10861091
PyList_Append:PyObject*:item:+1:

Doc/whatsnew/3.10.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,10 @@ New Features
314314
search function.
315315
(Contributed by Hai Shi in :issue:`41842`.)
316316

317+
* The :c:func:`PyIter_Send` and :c:func:`PyGen_Send` functions were added to allow
318+
sending value into iterator without raising ``StopIteration`` exception.
319+
(Contributed by Vladimir Matveev in :issue:`41756`.)
320+
317321
Porting to Python 3.10
318322
----------------------
319323

Include/abstract.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,22 @@ PyAPI_FUNC(int) PyIter_Check(PyObject *);
338338
NULL with an exception means an error occurred. */
339339
PyAPI_FUNC(PyObject *) PyIter_Next(PyObject *);
340340

341+
typedef enum {
342+
PYGEN_RETURN = 0,
343+
PYGEN_ERROR = -1,
344+
PYGEN_NEXT = 1,
345+
} PySendResult;
346+
347+
/* Takes generator, coroutine or iterator object and sends the value into it.
348+
Returns:
349+
- PYGEN_RETURN (0) if generator has returned.
350+
'result' parameter is filled with return value
351+
- PYGEN_ERROR (-1) if exception was raised.
352+
'result' parameter is NULL
353+
- PYGEN_NEXT (1) if generator has yielded.
354+
'result' parameter is filled with yielded value. */
355+
PyAPI_FUNC(PySendResult) PyIter_Send(PyObject *, PyObject *, PyObject **);
356+
341357

342358
/* === Number Protocol ================================================== */
343359

Include/genobject.h

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ extern "C" {
99
#endif
1010

1111
#include "pystate.h" /* _PyErr_StackItem */
12+
#include "abstract.h" /* PySendResult */
1213

1314
/* _PyGenObject_HEAD defines the initial segment of generator
1415
and coroutine objects. */
@@ -41,16 +42,9 @@ PyAPI_FUNC(PyObject *) PyGen_NewWithQualName(PyFrameObject *,
4142
PyObject *name, PyObject *qualname);
4243
PyAPI_FUNC(int) _PyGen_SetStopIterationValue(PyObject *);
4344
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
44-
PyAPI_FUNC(PyObject *) _PyGen_Send(PyGenObject *, PyObject *);
4545
PyObject *_PyGen_yf(PyGenObject *);
4646
PyAPI_FUNC(void) _PyGen_Finalize(PyObject *self);
4747

48-
typedef enum {
49-
PYGEN_RETURN = 0,
50-
PYGEN_ERROR = -1,
51-
PYGEN_NEXT = 1,
52-
} PySendResult;
53-
5448
/* Sends the value into the generator or the coroutine. Returns:
5549
- PYGEN_RETURN (0) if generator has returned.
5650
'result' parameter is filled with return value
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add `PyIter_Send` function to allow sending value into
2+
generator/coroutine/iterator without raising StopIteration exception to
3+
signal return.

Modules/_asynciomodule.c

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ _Py_IDENTIFIER(add_done_callback);
1616
_Py_IDENTIFIER(call_soon);
1717
_Py_IDENTIFIER(cancel);
1818
_Py_IDENTIFIER(get_event_loop);
19-
_Py_IDENTIFIER(send);
2019
_Py_IDENTIFIER(throw);
2120

2221

@@ -2695,13 +2694,7 @@ task_step_impl(TaskObj *task, PyObject *exc)
26952694

26962695
int gen_status = PYGEN_ERROR;
26972696
if (exc == NULL) {
2698-
if (PyGen_CheckExact(coro) || PyCoro_CheckExact(coro)) {
2699-
gen_status = PyGen_Send((PyGenObject*)coro, Py_None, &result);
2700-
}
2701-
else {
2702-
result = _PyObject_CallMethodIdOneArg(coro, &PyId_send, Py_None);
2703-
gen_status = gen_status_from_result(&result);
2704-
}
2697+
gen_status = PyIter_Send(coro, Py_None, &result);
27052698
}
27062699
else {
27072700
result = _PyObject_CallMethodIdOneArg(coro, &PyId_throw, exc);

Modules/_testcapimodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5028,6 +5028,7 @@ dict_get_version(PyObject *self, PyObject *args)
50285028
static PyObject *
50295029
raise_SIGINT_then_send_None(PyObject *self, PyObject *args)
50305030
{
5031+
_Py_IDENTIFIER(send);
50315032
PyGenObject *gen;
50325033

50335034
if (!PyArg_ParseTuple(args, "O!", &PyGen_Type, &gen))
@@ -5044,7 +5045,7 @@ raise_SIGINT_then_send_None(PyObject *self, PyObject *args)
50445045
because we check for signals before every bytecode operation.
50455046
*/
50465047
raise(SIGINT);
5047-
return _PyGen_Send(gen, Py_None);
5048+
return _PyObject_CallMethodIdOneArg((PyObject *)gen, &PyId_send, Py_None);
50485049
}
50495050

50505051

Objects/abstract.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2669,6 +2669,30 @@ PyIter_Next(PyObject *iter)
26692669
return result;
26702670
}
26712671

2672+
PySendResult
2673+
PyIter_Send(PyObject *iter, PyObject *arg, PyObject **result)
2674+
{
2675+
_Py_IDENTIFIER(send);
2676+
assert(result != NULL);
2677+
2678+
if (PyGen_CheckExact(iter) || PyCoro_CheckExact(iter)) {
2679+
return PyGen_Send((PyGenObject *)iter, arg, result);
2680+
}
2681+
2682+
if (arg == Py_None && PyIter_Check(iter)) {
2683+
*result = Py_TYPE(iter)->tp_iternext(iter);
2684+
}
2685+
else {
2686+
*result = _PyObject_CallMethodIdOneArg(iter, &PyId_send, arg);
2687+
}
2688+
if (*result != NULL) {
2689+
return PYGEN_NEXT;
2690+
}
2691+
if (_PyGen_FetchStopIterationValue(result) == 0) {
2692+
return PYGEN_RETURN;
2693+
}
2694+
return PYGEN_ERROR;
2695+
}
26722696

26732697
/*
26742698
* Flatten a sequence of bytes() objects into a C array of

Objects/genobject.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -308,12 +308,6 @@ gen_send(PyGenObject *gen, PyObject *arg)
308308
return gen_send_ex(gen, arg, 0, 0);
309309
}
310310

311-
PyObject *
312-
_PyGen_Send(PyGenObject *gen, PyObject *arg)
313-
{
314-
return gen_send(gen, arg);
315-
}
316-
317311
PyDoc_STRVAR(close_doc,
318312
"close() -> raise GeneratorExit inside generator.");
319313

@@ -1012,7 +1006,7 @@ PyDoc_STRVAR(coro_close_doc,
10121006
"close() -> raise GeneratorExit inside coroutine.");
10131007

10141008
static PyMethodDef coro_methods[] = {
1015-
{"send",(PyCFunction)_PyGen_Send, METH_O, coro_send_doc},
1009+
{"send",(PyCFunction)gen_send, METH_O, coro_send_doc},
10161010
{"throw",(PyCFunction)gen_throw, METH_VARARGS, coro_throw_doc},
10171011
{"close",(PyCFunction)gen_close, METH_NOARGS, coro_close_doc},
10181012
{NULL, NULL} /* Sentinel */

Python/ceval.c

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,24 +2210,17 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
22102210
case TARGET(YIELD_FROM): {
22112211
PyObject *v = POP();
22122212
PyObject *receiver = TOP();
2213-
int is_gen_or_coro = PyGen_CheckExact(receiver) || PyCoro_CheckExact(receiver);
2214-
int gen_status;
2215-
if (tstate->c_tracefunc == NULL && is_gen_or_coro) {
2216-
gen_status = PyGen_Send((PyGenObject *)receiver, v, &retval);
2213+
PySendResult gen_status;
2214+
if (tstate->c_tracefunc == NULL) {
2215+
gen_status = PyIter_Send(receiver, v, &retval);
22172216
} else {
2218-
if (is_gen_or_coro) {
2219-
retval = _PyGen_Send((PyGenObject *)receiver, v);
2217+
_Py_IDENTIFIER(send);
2218+
if (v == Py_None && PyIter_Check(receiver)) {
2219+
retval = Py_TYPE(receiver)->tp_iternext(receiver);
22202220
}
22212221
else {
2222-
_Py_IDENTIFIER(send);
2223-
if (v == Py_None) {
2224-
retval = Py_TYPE(receiver)->tp_iternext(receiver);
2225-
}
2226-
else {
2227-
retval = _PyObject_CallMethodIdOneArg(receiver, &PyId_send, v);
2228-
}
2222+
retval = _PyObject_CallMethodIdOneArg(receiver, &PyId_send, v);
22292223
}
2230-
22312224
if (retval == NULL) {
22322225
if (tstate->c_tracefunc != NULL
22332226
&& _PyErr_ExceptionMatches(tstate, PyExc_StopIteration))

0 commit comments

Comments
 (0)
0