diff -r b322047fec55 Include/dictobject.h --- a/Include/dictobject.h Wed Oct 23 22:27:52 2013 +0300 +++ b/Include/dictobject.h Wed Oct 23 22:30:53 2013 +0300 @@ -25,6 +25,7 @@ Py_ssize_t ma_used; PyDictKeysObject *ma_keys; PyObject **ma_values; + size_t ma_count; } PyDictObject; #endif /* Py_LIMITED_API */ diff -r b322047fec55 Lib/test/test_dict.py --- a/Lib/test/test_dict.py Wed Oct 23 22:27:52 2013 +0300 +++ b/Lib/test/test_dict.py Wed Oct 23 22:30:53 2013 +0300 @@ -414,12 +414,25 @@ self.assertRaises(Exc, d.pop, x) def test_mutating_iteration(self): - # changing dict size during iteration - d = {} - d[1] = 1 + # changing dict during iteration + d = {i: i for i in range(100)} with self.assertRaises(RuntimeError): for i in d: - d[i+1] = 1 + d[i + 100] = i + 100 + del d[i] + + d = {i: i for i in range(100)} + with self.assertRaises(RuntimeError): + for i, j in d.items(): + d[i + 100] = i + 100 + del d[i] + + d = {i: i for i in range(100)} + with self.assertRaises(RuntimeError): + for i in d.values(): + d[i + 100] = i + 100 + del d[i] + def test_mutating_lookup(self): # changing dict during a lookup (issue #14417) diff -r b322047fec55 Objects/dictobject.c --- a/Objects/dictobject.c Wed Oct 23 22:27:52 2013 +0300 +++ b/Objects/dictobject.c Wed Oct 23 22:30:53 2013 +0300 @@ -831,12 +831,14 @@ } mp->ma_keys->dk_usable--; assert(mp->ma_keys->dk_usable >= 0); + mp->ma_count++; ep->me_key = key; ep->me_hash = hash; } else { if (ep->me_key == dummy) { Py_INCREF(key); + mp->ma_count++; ep->me_key = key; ep->me_hash = hash; Py_DECREF(dummy); @@ -925,6 +927,7 @@ mp->ma_keys = oldkeys; return -1; } + mp->ma_count++; if (oldkeys->dk_lookup == lookdict) mp->ma_keys->dk_lookup = lookdict; oldsize = DK_SIZE(oldkeys); @@ -956,6 +959,7 @@ } } mp->ma_keys->dk_usable -= mp->ma_used; + mp->ma_count++; if (oldvalues != NULL) { /* NULL out me_value slot in oldkeys, in case it was shared */ for (i = 0; i < oldsize; i++) @@ -1237,6 +1241,7 @@ ENSURE_ALLOWS_DELETIONS(mp); old_key = ep->me_key; Py_INCREF(dummy); + mp->ma_count++; ep->me_key = dummy; Py_DECREF(old_key); } @@ -2268,6 +2273,7 @@ *value_addr = defaultobj; val = defaultobj; mp->ma_keys->dk_usable--; + mp->ma_count++; mp->ma_used++; } return val; @@ -2335,6 +2341,7 @@ mp->ma_used--; if (!_PyDict_HasSplitTable(mp)) { ENSURE_ALLOWS_DELETIONS(mp); + mp->ma_count++; old_key = ep->me_key; Py_INCREF(dummy); ep->me_key = dummy; @@ -2403,6 +2410,7 @@ PyTuple_SET_ITEM(res, 0, ep->me_key); PyTuple_SET_ITEM(res, 1, ep->me_value); Py_INCREF(dummy); + mp->ma_count++; ep->me_key = dummy; ep->me_value = NULL; mp->ma_used--; @@ -2753,7 +2761,7 @@ typedef struct { PyObject_HEAD PyDictObject *di_dict; /* Set to NULL when iterator is exhausted */ - Py_ssize_t di_used; + size_t di_count; Py_ssize_t di_pos; PyObject* di_result; /* reusable result tuple for iteritems */ Py_ssize_t len; @@ -2768,7 +2776,7 @@ return NULL; Py_INCREF(dict); di->di_dict = dict; - di->di_used = dict->ma_used; + di->di_count = dict->ma_count; di->di_pos = 0; di->len = dict->ma_used; if (itertype == &PyDictIterItem_Type) { @@ -2804,7 +2812,7 @@ dictiter_len(dictiterobject *di) { Py_ssize_t len = 0; - if (di->di_dict != NULL && di->di_used == di->di_dict->ma_used) + if (di->di_dict != NULL && di->di_count == di->di_dict->ma_count) len = di->len; return PyLong_FromSize_t(len); } @@ -2837,10 +2845,10 @@ return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) { + if (di->di_count != d->ma_count) { PyErr_SetString(PyExc_RuntimeError, - "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ + "dictionary was changed during iteration"); + di->di_count = d->ma_count - 1; /* Make this state sticky */ return NULL; } @@ -2919,10 +2927,10 @@ return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) { + if (di->di_count != d->ma_count) { PyErr_SetString(PyExc_RuntimeError, - "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ + "dictionary was changed during iteration"); + di->di_count = d->ma_count - 1; /* Make this state sticky */ return NULL; } @@ -3000,10 +3008,10 @@ return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) { + if (di->di_count != d->ma_count) { PyErr_SetString(PyExc_RuntimeError, - "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ + "dictionary was changed during iteration"); + di->di_count = d->ma_count - 1; /* Make this state sticky */ return NULL; }