Skip to content

Commit d85e8ce

Browse files
Always set it_stop_exc for a non-exhausted iterator
Use StopIteration (StopAsyncIteration for aiter()) as the default instead of normalizing it to NULL, so that the check is a single PyErr_ExceptionMatches(). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 2583789 commit d85e8ce

4 files changed

Lines changed: 31 additions & 71 deletions

File tree

Include/internal/pycore_iterobject.h

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,15 @@ extern PyTypeObject _PyACallIterAwaitable_Type;
1313

1414
// Like PyCallIter_New(), but the iteration also stops when *callable* raises
1515
// an exception matching *stop_exc* (an exception class or a tuple of exception
16-
// classes). Both *sentinel* and *stop_exc* can be NULL.
16+
// classes). *sentinel* can be NULL; NULL *stop_exc* means StopIteration.
1717
extern PyObject *_PyCallIter_NewEx(PyObject *callable, PyObject *sentinel,
1818
PyObject *stop_exc);
1919

2020
// The asynchronous counterpart of _PyCallIter_NewEx(): the result of
21-
// *callable* is awaited, and StopAsyncIteration stops the iteration.
21+
// *callable* is awaited, and NULL *stop_exc* means StopAsyncIteration.
2222
extern PyObject *_PyACallIter_New(PyObject *callable, PyObject *sentinel,
2323
PyObject *stop_exc);
2424

25-
// Return NULL if *stop_exc* has no effect: *implied_exc* stops the iteration
26-
// in any case, and an empty tuple never matches a raised exception.
27-
static inline PyObject *
28-
_PyIter_NormalizeStopException(PyObject *stop_exc, PyObject *implied_exc)
29-
{
30-
if (stop_exc == implied_exc ||
31-
(PyTuple_Check(stop_exc) && PyTuple_GET_SIZE(stop_exc) == 0))
32-
{
33-
return NULL;
34-
}
35-
return stop_exc;
36-
}
37-
3825
#ifdef __cplusplus
3926
}
4027
#endif

Lib/test/test_iter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,9 @@ def test_calliter_reduce(self):
419419
self.assertEqual(iter(c, 10, stop_exception=StopIteration).__reduce__(),
420420
(iter, (c, 10)))
421421
self.assertEqual(iter(c, 10, stop_exception=()).__reduce__(),
422-
(iter, (c, 10)))
422+
(iter, (c, None), ((10,), ())))
423423
self.assertEqual(iter(c, stop_exception=StopIteration).__reduce__(),
424-
(iter, (c, None), ((), ())))
424+
(iter, (c, None), ((), StopIteration)))
425425
self.assertEqual(iter(c, stop_exception=IndexError).__reduce__(),
426426
(iter, (c, None), ((), IndexError)))
427427
self.assertEqual(iter(c, 10, stop_exception=IndexError).__reduce__(),
@@ -439,10 +439,10 @@ def test_calliter_setstate(self):
439439
it.__setstate__(((10,), StopIteration))
440440
self.assertEqual(it.__reduce__(), (iter, (c, 10)))
441441
it.__setstate__(((10,), ()))
442-
self.assertEqual(it.__reduce__(), (iter, (c, 10)))
442+
self.assertEqual(it.__reduce__(), (iter, (c, None), ((10,), ())))
443443
it.__setstate__(((), IndexError))
444444
self.assertEqual(it.__reduce__(), (iter, (c, None), ((), IndexError)))
445-
it.__setstate__(((10,), ()))
445+
it.__setstate__(((10,), StopIteration))
446446
self.assertEqual(list(it), list(range(10)))
447447

448448
def test_iter_function_concealing_reentrant_exhaustion(self):

Objects/iterobject.c

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -189,25 +189,26 @@ typedef struct {
189189
PyObject_HEAD
190190
/* All are set to NULL when the iterator is exhausted */
191191
PyObject *it_callable;
192-
PyObject *it_sentinel; /* can be NULL */
193-
PyObject *it_stop_exc; /* can be NULL */
192+
PyObject *it_sentinel; /* can be NULL */
193+
PyObject *it_stop_exc; /* not NULL if it_callable is not NULL */
194194
} calliterobject;
195195

196196
PyObject *
197197
_PyCallIter_NewEx(PyObject *callable, PyObject *sentinel, PyObject *stop_exc)
198198
{
199199
calliterobject *it;
200-
if (stop_exc != NULL &&
201-
_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0)
202-
{
200+
if (stop_exc == NULL) {
201+
stop_exc = PyExc_StopIteration;
202+
}
203+
else if (_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0) {
203204
return NULL;
204205
}
205206
it = PyObject_GC_New(calliterobject, &PyCallIter_Type);
206207
if (it == NULL)
207208
return NULL;
208209
it->it_callable = Py_NewRef(callable);
209210
it->it_sentinel = Py_XNewRef(sentinel);
210-
it->it_stop_exc = Py_XNewRef(stop_exc);
211+
it->it_stop_exc = Py_NewRef(stop_exc);
211212
_PyObject_GC_TRACK(it);
212213
return (PyObject *)it;
213214
}
@@ -266,10 +267,7 @@ calliter_iternext(PyObject *op)
266267
Py_CLEAR(it->it_stop_exc);
267268
}
268269
}
269-
else if ((it->it_stop_exc != NULL &&
270-
PyErr_ExceptionMatches(it->it_stop_exc)) ||
271-
PyErr_ExceptionMatches(PyExc_StopIteration))
272-
{
270+
else if (PyErr_ExceptionMatches(it->it_stop_exc)) {
273271
PyErr_Clear();
274272
Py_CLEAR(it->it_callable);
275273
Py_CLEAR(it->it_sentinel);
@@ -295,23 +293,15 @@ calliter_reduce(PyObject *op, PyObject *Py_UNUSED(ignored))
295293
/* Only the sentinel can be passed as an argument of iter(), so other
296294
attributes are restored from the state (see calliter_setstate()). */
297295
if (it->it_sentinel == NULL) {
298-
if (it->it_stop_exc == NULL) {
299-
return Py_BuildValue("N(OO)(()())", iter, it->it_callable, Py_None);
300-
}
301-
else {
302-
return Py_BuildValue("N(OO)(()O)", iter, it->it_callable, Py_None,
303-
it->it_stop_exc);
304-
}
296+
return Py_BuildValue("N(OO)(()O)", iter, it->it_callable, Py_None,
297+
it->it_stop_exc);
298+
}
299+
else if (it->it_stop_exc == PyExc_StopIteration) {
300+
return Py_BuildValue("N(OO)", iter, it->it_callable, it->it_sentinel);
305301
}
306302
else {
307-
if (it->it_stop_exc == NULL) {
308-
return Py_BuildValue("N(OO)", iter, it->it_callable,
309-
it->it_sentinel);
310-
}
311-
else {
312-
return Py_BuildValue("N(OO)((O)O)", iter, it->it_callable, Py_None,
313-
it->it_sentinel, it->it_stop_exc);
314-
}
303+
return Py_BuildValue("N(OO)((O)O)", iter, it->it_callable, Py_None,
304+
it->it_sentinel, it->it_stop_exc);
315305
}
316306
}
317307

@@ -332,12 +322,11 @@ calliter_setstate(PyObject *op, PyObject *state)
332322
if (_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0) {
333323
return NULL;
334324
}
335-
stop_exc = _PyIter_NormalizeStopException(stop_exc, PyExc_StopIteration);
336325
if (it->it_callable != NULL) {
337326
Py_XSETREF(it->it_sentinel,
338327
PyTuple_GET_SIZE(sentinel) ?
339328
Py_NewRef(PyTuple_GET_ITEM(sentinel, 0)) : NULL);
340-
Py_XSETREF(it->it_stop_exc, Py_XNewRef(stop_exc));
329+
Py_SETREF(it->it_stop_exc, Py_NewRef(stop_exc));
341330
}
342331
Py_RETURN_NONE;
343332

@@ -629,8 +618,8 @@ typedef struct {
629618
PyObject_HEAD
630619
/* All are set to NULL when the iterator is exhausted */
631620
PyObject *it_callable;
632-
PyObject *it_sentinel; /* can be NULL */
633-
PyObject *it_stop_exc; /* can be NULL */
621+
PyObject *it_sentinel; /* can be NULL */
622+
PyObject *it_stop_exc; /* not NULL if it_callable is not NULL */
634623
} acalliterobject;
635624

636625
#define acalliterobject_CAST(op) ((acalliterobject *)(op))
@@ -649,9 +638,10 @@ typedef struct {
649638
PyObject *
650639
_PyACallIter_New(PyObject *callable, PyObject *sentinel, PyObject *stop_exc)
651640
{
652-
if (stop_exc != NULL &&
653-
_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0)
654-
{
641+
if (stop_exc == NULL) {
642+
stop_exc = PyExc_StopAsyncIteration;
643+
}
644+
else if (_PyEval_CheckExceptTypeValid(_PyThreadState_GET(), stop_exc) < 0) {
655645
return NULL;
656646
}
657647
acalliterobject *it = PyObject_GC_New(acalliterobject, &_PyACallIter_Type);
@@ -660,7 +650,7 @@ _PyACallIter_New(PyObject *callable, PyObject *sentinel, PyObject *stop_exc)
660650
}
661651
it->it_callable = Py_NewRef(callable);
662652
it->it_sentinel = Py_XNewRef(sentinel);
663-
it->it_stop_exc = Py_XNewRef(stop_exc);
653+
it->it_stop_exc = Py_NewRef(stop_exc);
664654
_PyObject_GC_TRACK(it);
665655
return (PyObject *)it;
666656
}
@@ -673,15 +663,6 @@ acalliter_exhaust(acalliterobject *it)
673663
Py_CLEAR(it->it_stop_exc);
674664
}
675665

676-
/* Return 1 if the raised exception ends the iteration. */
677-
static int
678-
acalliter_stop_matches(acalliterobject *it)
679-
{
680-
return ((it->it_stop_exc != NULL &&
681-
PyErr_ExceptionMatches(it->it_stop_exc)) ||
682-
PyErr_ExceptionMatches(PyExc_StopAsyncIteration));
683-
}
684-
685666
static void
686667
acalliter_dealloc(PyObject *op)
687668
{
@@ -797,7 +778,7 @@ acallawaitable_start(acallawaitableobject *aw)
797778
}
798779
PyObject *awaitable = _PyObject_CallNoArgs(it->it_callable);
799780
if (awaitable == NULL) {
800-
if (acalliter_stop_matches(it)) {
781+
if (PyErr_ExceptionMatches(it->it_stop_exc)) {
801782
PyErr_Clear();
802783
acalliter_exhaust(it);
803784
PyErr_SetNone(PyExc_StopAsyncIteration);
@@ -834,7 +815,7 @@ acallawaitable_handle_error(acallawaitableobject *aw)
834815
Py_DECREF(value);
835816
return NULL;
836817
}
837-
if (acalliter_stop_matches(it)) {
818+
if (PyErr_ExceptionMatches(it->it_stop_exc)) {
838819
PyErr_Clear();
839820
acalliter_exhaust(it);
840821
PyErr_SetNone(PyExc_StopAsyncIteration);

Python/bltinmodule.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,10 +1924,6 @@ builtin_iter_impl(PyObject *module, PyObject *object, PyObject *stop_value,
19241924
"iter(): the first argument must be callable");
19251925
return NULL;
19261926
}
1927-
if (stop_exception != NULL) {
1928-
stop_exception = _PyIter_NormalizeStopException(stop_exception,
1929-
PyExc_StopIteration);
1930-
}
19311927
return _PyCallIter_NewEx(object, stop_value, stop_exception);
19321928
}
19331929

@@ -1962,10 +1958,6 @@ builtin_aiter_impl(PyObject *module, PyObject *object, PyObject *stop_value,
19621958
"aiter(): the first argument must be callable");
19631959
return NULL;
19641960
}
1965-
if (stop_exception != NULL) {
1966-
stop_exception = _PyIter_NormalizeStopException(
1967-
stop_exception, PyExc_StopAsyncIteration);
1968-
}
19691961
return _PyACallIter_New(object, stop_value, stop_exception);
19701962
}
19711963

0 commit comments

Comments
 (0)