Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-105201: Add PyIter_NextItem()#122331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
gh-105201: Add PyIter_NextItem() #122331
Changes from all commits
bde5ed31304507210c0e1c778d62c5800e6f60173b36cd91ad9e3a38636e39bfd584c59a6b2460f82039fe41ad7File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -10,7 +10,8 @@ There are two functions specifically for working with iterators. | ||
| .. c:function:: int PyIter_Check(PyObject *o) | ||
| 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) | ||
| @@ -19,41 +20,27 @@ There are two functions specifically for working with iterators. | ||
| .. 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) | ||
| This is an older version of :c:func:`!PyIter_NextItem`, | ||
erlend-aasland marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| 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 | ||
| 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 an error occurs while retrieving the item, returns ``NULL`` and passes | ||
| 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 | ||
| The enum value used to represent different results of :c:func:`PyIter_Send`. | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1007,6 +1007,46 @@ def test_object_generichash(self): | ||
| for obj in object(), 1, 'string', []: | ||
| 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": | ||
erlend-aasland marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| 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) | ||
erlend-aasland marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| def test_iter_nextitem(self): | ||
| from _testcapi import PyIter_NextItem | ||
| self.run_iter_api_test(PyIter_NextItem) | ||
| regex = "expected.*iterator.*got.*'int'" | ||
picnixz marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| with self.assertRaisesRegex(TypeError, regex): | ||
| PyIter_NextItem(10) | ||
| if __name__ == "__main__": | ||
| unittest.main() | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2508,3 +2508,5 @@ | ||
| [function.Py_TYPE] | ||
| added = '3.14' | ||
| [function.PyIter_NextItem] | ||
| added = '3.14' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -2881,7 +2881,50 @@ PyAIter_Check(PyObject *obj) | ||
| tp->tp_as_async->am_anext != &_PyObject_NextNotImplemented); | ||
| } | ||
| static int | ||
picnixz marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| 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. | ||
| * | ||
| * If an error occurs, return NULL. PyErr_Occurred() will be true. | ||
| * If the iteration terminates normally, return NULL and clear the | ||
| * PyExc_StopIteration exception (if it was set). PyErr_Occurred() | ||
| @@ -2891,17 +2934,9 @@ PyAIter_Check(PyObject *obj) | ||
| PyObject * | ||
| PyIter_Next(PyObject *iter) | ||
| { | ||
| PyObject *result; | ||
| result = (*Py_TYPE(iter)->tp_iternext)(iter); | ||
| if (result == NULL){ | ||
| PyThreadState *tstate = _PyThreadState_GET(); | ||
| if (_PyErr_Occurred(tstate) | ||
| && _PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) | ||
| { | ||
| _PyErr_Clear(tstate); | ||
| } | ||
| } | ||
| return result; | ||
| PyObject *item; | ||
| (void)iternext(iter, &item); | ||
| return item; | ||
| } | ||
| PySendResult | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.