mirror of https://github.com/python/cpython
gh-105201: Add PyIter_NextItem() (#122331)
Return -1 and set an exception on error; return 0 if the iterator is exhausted, and return 1 if the next item was fetched successfully. Prefer this API to PyIter_Next(), which requires the caller to use PyErr_Occurred() to differentiate between iterator exhaustion and errors. Co-authered-by: Irit Katriel <iritkatriel@yahoo.com>
This commit is contained in:
parent
540fcc62f5
commit
e006c7371d
|
@ -10,7 +10,8 @@ There are two functions specifically for working with iterators.
|
||||||
.. c:function:: int PyIter_Check(PyObject *o)
|
.. c:function:: int PyIter_Check(PyObject *o)
|
||||||
|
|
||||||
Return non-zero if the object *o* can be safely passed to
|
Return non-zero if the object *o* can be safely passed to
|
||||||
:c:func:`PyIter_Next`, and ``0`` otherwise. This function always succeeds.
|
:c:func:`PyIter_NextItem` and ``0`` otherwise.
|
||||||
|
This function always succeeds.
|
||||||
|
|
||||||
.. c:function:: int PyAIter_Check(PyObject *o)
|
.. c:function:: int PyAIter_Check(PyObject *o)
|
||||||
|
|
||||||
|
@ -19,41 +20,27 @@ There are two functions specifically for working with iterators.
|
||||||
|
|
||||||
.. versionadded:: 3.10
|
.. versionadded:: 3.10
|
||||||
|
|
||||||
|
.. c:function:: int PyIter_NextItem(PyObject *iter, PyObject **item)
|
||||||
|
|
||||||
|
Return ``1`` and set *item* to a :term:`strong reference` of the
|
||||||
|
next value of the iterator *iter* on success.
|
||||||
|
Return ``0`` and set *item* to ``NULL`` if there are no remaining values.
|
||||||
|
Return ``-1``, set *item* to ``NULL`` and set an exception on error.
|
||||||
|
|
||||||
|
.. versionadded:: 3.14
|
||||||
|
|
||||||
.. c:function:: PyObject* PyIter_Next(PyObject *o)
|
.. c:function:: PyObject* PyIter_Next(PyObject *o)
|
||||||
|
|
||||||
|
This is an older version of :c:func:`!PyIter_NextItem`,
|
||||||
|
which is retained for backwards compatibility.
|
||||||
|
Prefer :c:func:`PyIter_NextItem`.
|
||||||
|
|
||||||
Return the next value from the iterator *o*. The object must be an iterator
|
Return the next value from the iterator *o*. The object must be an iterator
|
||||||
according to :c:func:`PyIter_Check` (it is up to the caller to check this).
|
according to :c:func:`PyIter_Check` (it is up to the caller to check this).
|
||||||
If there are no remaining values, returns ``NULL`` with no exception set.
|
If there are no remaining values, returns ``NULL`` with no exception set.
|
||||||
If an error occurs while retrieving the item, returns ``NULL`` and passes
|
If an error occurs while retrieving the item, returns ``NULL`` and passes
|
||||||
along the exception.
|
along the exception.
|
||||||
|
|
||||||
To write a loop which iterates over an iterator, the C code should look
|
|
||||||
something like this::
|
|
||||||
|
|
||||||
PyObject *iterator = PyObject_GetIter(obj);
|
|
||||||
PyObject *item;
|
|
||||||
|
|
||||||
if (iterator == NULL) {
|
|
||||||
/* propagate error */
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((item = PyIter_Next(iterator))) {
|
|
||||||
/* do something with item */
|
|
||||||
...
|
|
||||||
/* release reference when done */
|
|
||||||
Py_DECREF(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_DECREF(iterator);
|
|
||||||
|
|
||||||
if (PyErr_Occurred()) {
|
|
||||||
/* propagate error */
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* continue doing useful work */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.. c:type:: PySendResult
|
.. c:type:: PySendResult
|
||||||
|
|
||||||
The enum value used to represent different results of :c:func:`PyIter_Send`.
|
The enum value used to represent different results of :c:func:`PyIter_Send`.
|
||||||
|
|
|
@ -1132,6 +1132,10 @@ PyAIter_Check:PyObject*:o:0:
|
||||||
PyIter_Next:PyObject*::+1:
|
PyIter_Next:PyObject*::+1:
|
||||||
PyIter_Next:PyObject*:o:0:
|
PyIter_Next:PyObject*:o:0:
|
||||||
|
|
||||||
|
PyIter_NextItem:int:::
|
||||||
|
PyIter_NextItem:PyObject*:iter:0:
|
||||||
|
PyIter_NextItem:PyObject**:item:+1:
|
||||||
|
|
||||||
PyIter_Send:int:::
|
PyIter_Send:int:::
|
||||||
PyIter_Send:PyObject*:iter:0:
|
PyIter_Send:PyObject*:iter:0:
|
||||||
PyIter_Send:PyObject*:arg:0:
|
PyIter_Send:PyObject*:arg:0:
|
||||||
|
|
|
@ -335,6 +335,7 @@ func,PyInterpreterState_GetID,3.7,,
|
||||||
func,PyInterpreterState_New,3.2,,
|
func,PyInterpreterState_New,3.2,,
|
||||||
func,PyIter_Check,3.8,,
|
func,PyIter_Check,3.8,,
|
||||||
func,PyIter_Next,3.2,,
|
func,PyIter_Next,3.2,,
|
||||||
|
func,PyIter_NextItem,3.14,,
|
||||||
func,PyIter_Send,3.10,,
|
func,PyIter_Send,3.10,,
|
||||||
data,PyListIter_Type,3.2,,
|
data,PyListIter_Type,3.2,,
|
||||||
data,PyListRevIter_Type,3.2,,
|
data,PyListRevIter_Type,3.2,,
|
||||||
|
|
|
@ -404,6 +404,10 @@ New Features
|
||||||
|
|
||||||
(Contributed by Victor Stinner in :gh:`119182`.)
|
(Contributed by Victor Stinner in :gh:`119182`.)
|
||||||
|
|
||||||
|
* Add :c:func:`PyIter_NextItem` to replace :c:func:`PyIter_Next`,
|
||||||
|
which has an ambiguous return value.
|
||||||
|
(Contributed by Irit Katriel and Erlend Aasland in :gh:`105201`.)
|
||||||
|
|
||||||
Porting to Python 3.14
|
Porting to Python 3.14
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -397,13 +397,23 @@ PyAPI_FUNC(int) PyIter_Check(PyObject *);
|
||||||
This function always succeeds. */
|
This function always succeeds. */
|
||||||
PyAPI_FUNC(int) PyAIter_Check(PyObject *);
|
PyAPI_FUNC(int) PyAIter_Check(PyObject *);
|
||||||
|
|
||||||
|
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
|
||||||
|
/* Return 1 and set 'item' to the next item of 'iter' on success.
|
||||||
|
* Return 0 and set 'item' to NULL when there are no remaining values.
|
||||||
|
* Return -1, set 'item' to NULL and set an exception on error.
|
||||||
|
*/
|
||||||
|
PyAPI_FUNC(int) PyIter_NextItem(PyObject *iter, PyObject **item);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Takes an iterator object and calls its tp_iternext slot,
|
/* Takes an iterator object and calls its tp_iternext slot,
|
||||||
returning the next value.
|
returning the next value.
|
||||||
|
|
||||||
If the iterator is exhausted, this returns NULL without setting an
|
If the iterator is exhausted, this returns NULL without setting an
|
||||||
exception.
|
exception.
|
||||||
|
|
||||||
NULL with an exception means an error occurred. */
|
NULL with an exception means an error occurred.
|
||||||
|
|
||||||
|
Prefer PyIter_NextItem() instead. */
|
||||||
PyAPI_FUNC(PyObject *) PyIter_Next(PyObject *);
|
PyAPI_FUNC(PyObject *) PyIter_Next(PyObject *);
|
||||||
|
|
||||||
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
|
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000
|
||||||
|
|
|
@ -1007,6 +1007,46 @@ class CAPITest(unittest.TestCase):
|
||||||
for obj in object(), 1, 'string', []:
|
for obj in object(), 1, 'string', []:
|
||||||
self.assertEqual(generichash(obj), object.__hash__(obj))
|
self.assertEqual(generichash(obj), object.__hash__(obj))
|
||||||
|
|
||||||
|
def run_iter_api_test(self, next_func):
|
||||||
|
for data in (), [], (1, 2, 3), [1 , 2, 3], "123":
|
||||||
|
with self.subTest(data=data):
|
||||||
|
items = []
|
||||||
|
it = iter(data)
|
||||||
|
while (item := next_func(it)) is not None:
|
||||||
|
items.append(item)
|
||||||
|
self.assertEqual(items, list(data))
|
||||||
|
|
||||||
|
class Broken:
|
||||||
|
def __init__(self):
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
if self.count < 3:
|
||||||
|
self.count += 1
|
||||||
|
return self.count
|
||||||
|
else:
|
||||||
|
raise TypeError('bad type')
|
||||||
|
|
||||||
|
it = Broken()
|
||||||
|
self.assertEqual(next_func(it), 1)
|
||||||
|
self.assertEqual(next_func(it), 2)
|
||||||
|
self.assertEqual(next_func(it), 3)
|
||||||
|
with self.assertRaisesRegex(TypeError, 'bad type'):
|
||||||
|
next_func(it)
|
||||||
|
|
||||||
|
def test_iter_next(self):
|
||||||
|
from _testcapi import PyIter_Next
|
||||||
|
self.run_iter_api_test(PyIter_Next)
|
||||||
|
# CRASHES PyIter_Next(10)
|
||||||
|
|
||||||
|
def test_iter_nextitem(self):
|
||||||
|
from _testcapi import PyIter_NextItem
|
||||||
|
self.run_iter_api_test(PyIter_NextItem)
|
||||||
|
|
||||||
|
regex = "expected.*iterator.*got.*'int'"
|
||||||
|
with self.assertRaisesRegex(TypeError, regex):
|
||||||
|
PyIter_NextItem(10)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -371,6 +371,7 @@ SYMBOL_NAMES = (
|
||||||
"PyInterpreterState_New",
|
"PyInterpreterState_New",
|
||||||
"PyIter_Check",
|
"PyIter_Check",
|
||||||
"PyIter_Next",
|
"PyIter_Next",
|
||||||
|
"PyIter_NextItem",
|
||||||
"PyIter_Send",
|
"PyIter_Send",
|
||||||
"PyListIter_Type",
|
"PyListIter_Type",
|
||||||
"PyListRevIter_Type",
|
"PyListRevIter_Type",
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Add :c:func:`PyIter_NextItem` to replace :c:func:`PyIter_Next`, which has an
|
||||||
|
ambiguous return value. Patch by Irit Katriel and Erlend Aasland.
|
|
@ -2508,3 +2508,5 @@
|
||||||
|
|
||||||
[function.Py_TYPE]
|
[function.Py_TYPE]
|
||||||
added = '3.14'
|
added = '3.14'
|
||||||
|
[function.PyIter_NextItem]
|
||||||
|
added = '3.14'
|
||||||
|
|
|
@ -129,6 +129,33 @@ mapping_getoptionalitem(PyObject *self, PyObject *args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
pyiter_next(PyObject *self, PyObject *iter)
|
||||||
|
{
|
||||||
|
PyObject *item = PyIter_Next(iter);
|
||||||
|
if (item == NULL && !PyErr_Occurred()) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
pyiter_nextitem(PyObject *self, PyObject *iter)
|
||||||
|
{
|
||||||
|
PyObject *item;
|
||||||
|
int rc = PyIter_NextItem(iter, &item);
|
||||||
|
if (rc < 0) {
|
||||||
|
assert(PyErr_Occurred());
|
||||||
|
assert(item == NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
assert(!PyErr_Occurred());
|
||||||
|
if (item == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef test_methods[] = {
|
static PyMethodDef test_methods[] = {
|
||||||
{"object_getoptionalattr", object_getoptionalattr, METH_VARARGS},
|
{"object_getoptionalattr", object_getoptionalattr, METH_VARARGS},
|
||||||
|
@ -138,6 +165,8 @@ static PyMethodDef test_methods[] = {
|
||||||
{"mapping_getoptionalitem", mapping_getoptionalitem, METH_VARARGS},
|
{"mapping_getoptionalitem", mapping_getoptionalitem, METH_VARARGS},
|
||||||
{"mapping_getoptionalitemstring", mapping_getoptionalitemstring, METH_VARARGS},
|
{"mapping_getoptionalitemstring", mapping_getoptionalitemstring, METH_VARARGS},
|
||||||
|
|
||||||
|
{"PyIter_Next", pyiter_next, METH_O},
|
||||||
|
{"PyIter_NextItem", pyiter_nextitem, METH_O},
|
||||||
{NULL},
|
{NULL},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2881,7 +2881,50 @@ PyAIter_Check(PyObject *obj)
|
||||||
tp->tp_as_async->am_anext != &_PyObject_NextNotImplemented);
|
tp->tp_as_async->am_anext != &_PyObject_NextNotImplemented);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
iternext(PyObject *iter, PyObject **item)
|
||||||
|
{
|
||||||
|
iternextfunc tp_iternext = Py_TYPE(iter)->tp_iternext;
|
||||||
|
if ((*item = tp_iternext(iter))) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyThreadState *tstate = _PyThreadState_GET();
|
||||||
|
/* When the iterator is exhausted it must return NULL;
|
||||||
|
* a StopIteration exception may or may not be set. */
|
||||||
|
if (!_PyErr_Occurred(tstate)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) {
|
||||||
|
_PyErr_Clear(tstate);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error case: an exception (different than StopIteration) is set. */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return 1 and set 'item' to the next item of 'iter' on success.
|
||||||
|
* Return 0 and set 'item' to NULL when there are no remaining values.
|
||||||
|
* Return -1, set 'item' to NULL and set an exception on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
PyIter_NextItem(PyObject *iter, PyObject **item)
|
||||||
|
{
|
||||||
|
assert(iter != NULL);
|
||||||
|
assert(item != NULL);
|
||||||
|
|
||||||
|
if (Py_TYPE(iter)->tp_iternext == NULL) {
|
||||||
|
*item = NULL;
|
||||||
|
PyErr_Format(PyExc_TypeError, "expected an iterator, got '%T'", iter);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iternext(iter, item);
|
||||||
|
}
|
||||||
|
|
||||||
/* Return next item.
|
/* Return next item.
|
||||||
|
*
|
||||||
* If an error occurs, return NULL. PyErr_Occurred() will be true.
|
* If an error occurs, return NULL. PyErr_Occurred() will be true.
|
||||||
* If the iteration terminates normally, return NULL and clear the
|
* If the iteration terminates normally, return NULL and clear the
|
||||||
* PyExc_StopIteration exception (if it was set). PyErr_Occurred()
|
* PyExc_StopIteration exception (if it was set). PyErr_Occurred()
|
||||||
|
@ -2891,17 +2934,9 @@ PyAIter_Check(PyObject *obj)
|
||||||
PyObject *
|
PyObject *
|
||||||
PyIter_Next(PyObject *iter)
|
PyIter_Next(PyObject *iter)
|
||||||
{
|
{
|
||||||
PyObject *result;
|
PyObject *item;
|
||||||
result = (*Py_TYPE(iter)->tp_iternext)(iter);
|
(void)iternext(iter, &item);
|
||||||
if (result == NULL) {
|
return item;
|
||||||
PyThreadState *tstate = _PyThreadState_GET();
|
|
||||||
if (_PyErr_Occurred(tstate)
|
|
||||||
&& _PyErr_ExceptionMatches(tstate, PyExc_StopIteration))
|
|
||||||
{
|
|
||||||
_PyErr_Clear(tstate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PySendResult
|
PySendResult
|
||||||
|
|
|
@ -326,6 +326,7 @@ EXPORT_FUNC(PyInterpreterState_GetID)
|
||||||
EXPORT_FUNC(PyInterpreterState_New)
|
EXPORT_FUNC(PyInterpreterState_New)
|
||||||
EXPORT_FUNC(PyIter_Check)
|
EXPORT_FUNC(PyIter_Check)
|
||||||
EXPORT_FUNC(PyIter_Next)
|
EXPORT_FUNC(PyIter_Next)
|
||||||
|
EXPORT_FUNC(PyIter_NextItem)
|
||||||
EXPORT_FUNC(PyIter_Send)
|
EXPORT_FUNC(PyIter_Send)
|
||||||
EXPORT_FUNC(PyList_Append)
|
EXPORT_FUNC(PyList_Append)
|
||||||
EXPORT_FUNC(PyList_AsTuple)
|
EXPORT_FUNC(PyList_AsTuple)
|
||||||
|
|
Loading…
Reference in New Issue