diff -r 2bbe7dc920de Lib/test/test_dict.py --- a/Lib/test/test_dict.py Sat Dec 10 17:55:31 2011 -0500 +++ b/Lib/test/test_dict.py Sun Dec 11 21:02:22 2011 +0100 @@ -299,6 +299,17 @@ x.fail = True self.assertRaises(Exc, d.setdefault, x, []) + def test_setdefault_atomic(self): + count = 0 + class Hashed(object): + def __hash__(self): + nonlocal count + count += 1 + return 42 + x = {} + x.setdefault(Hashed(), []) + self.assertEqual(count, 1) + def test_popitem(self): # dict.popitem() for copymode in -1, +1: diff -r 2bbe7dc920de Objects/dictobject.c --- a/Objects/dictobject.c Sat Dec 10 17:55:31 2011 -0500 +++ b/Objects/dictobject.c Sun Dec 11 21:02:22 2011 +0100 @@ -783,17 +783,11 @@ return ep->me_value; } -/* CAUTION: PyDict_SetItem() must guarantee that it won't resize the - * dictionary if it's merely replacing the value for an existing key. - * This means that it's safe to loop over a dictionary with PyDict_Next() - * and occasionally replace a value -- but you can't insert new keys or - * remove them. - */ -int -PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value) +static int +dict_set_item_using_hash(register PyObject *op, PyObject *key, Py_hash_t hash, + PyObject *value) { register PyDictObject *mp; - register Py_hash_t hash; register Py_ssize_t n_used; if (!PyDict_Check(op)) { @@ -803,13 +797,6 @@ assert(key); assert(value); mp = (PyDictObject *)op; - if (!PyUnicode_CheckExact(key) || - (hash = ((PyASCIIObject *) key)->hash) == -1) - { - hash = PyObject_Hash(key); - if (hash == -1) - return -1; - } assert(mp->ma_fill <= mp->ma_mask); /* at least one empty slot */ n_used = mp->ma_used; Py_INCREF(value); @@ -835,6 +822,29 @@ return dictresize(mp, (mp->ma_used > 50000 ? 2 : 4) * mp->ma_used); } +/* CAUTION: PyDict_SetItem() must guarantee that it won't resize the + * dictionary if it's merely replacing the value for an existing key. + * This means that it's safe to loop over a dictionary with PyDict_Next() + * and occasionally replace a value -- but you can't insert new keys or + * remove them. + */ +int +PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value) +{ + register Py_hash_t hash; + + assert(key); + assert(value); + if (!PyUnicode_CheckExact(key) || + (hash = ((PyASCIIObject *) key)->hash) == -1) + { + hash = PyObject_Hash(key); + if (hash == -1) + return -1; + } + return dict_set_item_using_hash(op, key, hash, value); +} + int PyDict_DelItem(PyObject *op, PyObject *key) { @@ -1806,7 +1816,7 @@ val = ep->me_value; if (val == NULL) { val = failobj; - if (PyDict_SetItem((PyObject*)mp, key, failobj)) + if (dict_set_item_using_hash((PyObject*)mp, key, hash, failobj)) val = NULL; } Py_XINCREF(val);