diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 1e665c86303078..7035e72cdda09c 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1424,6 +1424,31 @@ def test_reversed_dict_after_clear_and_restore(self): for it in iterators: self.assertEqual(list(it), []) + def test_reversed_dict_keys_changed_during_iteration(self): + d = dict.fromkeys(range(10)) + for i in range(7): + del d[i] + + iterators = ( + reversed(d), + reversed(d.keys()), + reversed(d.values()), + reversed(d.items()), + ) + for it in iterators: + next(it) + + # Same size as before, but with different keys below + # the iterators' current position. + d.clear() + d.update(dict.fromkeys(range(10))) + for i in range(3, 10): + del d[i] + + for it in iterators: + with self.assertRaisesRegex(RuntimeError, 'keys changed'): + list(it) + def test_dict_copy_order(self): # bpo-34320 od = collections.OrderedDict([('a', 1), ('b', 2)]) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-15-39-32.gh-issue-158254.qT7vRk.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-15-39-32.gh-issue-158254.qT7vRk.rst new file mode 100644 index 00000000000000..21908eea4a8236 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-15-39-32.gh-issue-158254.qT7vRk.rst @@ -0,0 +1,3 @@ +Reverse iterators over a :class:`dict` and its views now raise +:exc:`RuntimeError` if the dictionary's keys change during iteration, like +forward iterators, instead of yielding entries for the new keys. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 53dc90be4c91e2..b31cb8f14ebc70 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -6254,6 +6254,12 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject *self) value = entry_ptr->me_value; } } + // We found an element, but did not expect it + if (di->len == 0) { + PyErr_SetString(PyExc_RuntimeError, + "dictionary keys changed during iteration"); + goto fail; + } di->di_pos = i-1; di->len--;