diff -r bf7329190ca6 Lib/test/test_dict.py --- a/Lib/test/test_dict.py Sun Dec 11 22:31:09 2011 -0800 +++ b/Lib/test/test_dict.py Sun Dec 18 18:56:26 2011 +0100 @@ -299,6 +299,18 @@ x.fail = True self.assertRaises(Exc, d.setdefault, x, []) + def test_setdefault_atomic(self): + class Hashed(object): + def __init__(self): + self.count = 0 + def __hash__(self): + self.count += 1 + return 42 + hashed = Hashed() + x = {} + x.setdefault(hashed, []) + self.assertEqual(hashed.count, 1) + def test_popitem(self): # dict.popitem() for copymode in -1, +1: diff -r bf7329190ca6 Objects/dictobject.c --- a/Objects/dictobject.c Sun Dec 11 22:31:09 2011 -0800 +++ b/Objects/dictobject.c Sun Dec 18 18:56:26 2011 +0100 @@ -738,36 +738,16 @@ 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, long hash, + PyObject *value) { register PyDictObject *mp; - register long hash; register Py_ssize_t n_used; - if (!PyDict_Check(op)) { - PyErr_BadInternalCall(); - return -1; - } assert(key); assert(value); mp = (PyDictObject *)op; - if (PyString_CheckExact(key)) { - hash = ((PyStringObject *)key)->ob_shash; - if (hash == -1) - hash = PyObject_Hash(key); - } - else { - 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); @@ -793,6 +773,36 @@ 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 long hash; + + if (!PyDict_Check(op)) { + PyErr_BadInternalCall(); + return -1; + } + assert(key); + assert(value); + if (PyString_CheckExact(key)) { + hash = ((PyStringObject *)key)->ob_shash; + if (hash == -1) + hash = PyObject_Hash(key); + } + else { + 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) { @@ -1958,7 +1968,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);