Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 70d2717

Browse files
committed
Issue #13521: dict.setdefault() now does only one lookup for the given key, making it "atomic" for many purposes.
Patch by Filip Gruszczyński.
2 parents 6181b39 + e965d97 commit 70d2717

3 files changed

Lines changed: 93 additions & 42 deletions

File tree

Lib/test/test_dict.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,26 @@ def __hash__(self):
299299
x.fail = True
300300
self.assertRaises(Exc, d.setdefault, x, [])
301301

302+
def test_setdefault_atomic(self):
303+
# Issue #13521: setdefault() calls __hash__ and __eq__ only once.
304+
class Hashed(object):
305+
def __init__(self):
306+
self.hash_count = 0
307+
self.eq_count = 0
308+
def __hash__(self):
309+
self.hash_count += 1
310+
return 42
311+
def __eq__(self, other):
312+
self.eq_count += 1
313+
return id(self) == id(other)
314+
hashed1 = Hashed()
315+
y = {hashed1: 5}
316+
hashed2 = Hashed()
317+
y.setdefault(hashed2, [])
318+
self.assertEqual(hashed1.hash_count, 1)
319+
self.assertEqual(hashed2.hash_count, 1)
320+
self.assertEqual(hashed1.eq_count + hashed2.eq_count, 1)
321+
302322
def test_popitem(self):
303323
# dict.popitem()
304324
for copymode in -1, +1:

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #13521: dict.setdefault() now does only one lookup for the given key,
14+
making it "atomic" for many purposes. Patch by Filip Gruszczyński.
15+
1316
- PEP 409, Issue #6210: "raise X from None" is now supported as a means of
1417
suppressing the display of the chained exception context. The chained
1518
context still remains available as the __context__ attribute.

Objects/dictobject.c

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -517,27 +517,16 @@ _PyDict_MaybeUntrack(PyObject *op)
517517
_PyObject_GC_UNTRACK(op);
518518
}
519519

520-
521520
/*
522-
Internal routine to insert a new item into the table.
523-
Used both by the internal resize routine and by the public insert routine.
524-
Eats a reference to key and one to value.
525-
Returns -1 if an error occurred, or 0 on success.
521+
Internal routine to insert a new item into the table when you have entry object.
522+
Used by insertdict.
526523
*/
527524
static int
528-
insertdict(register PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
525+
insertdict_by_entry(register PyDictObject *mp, PyObject *key, Py_hash_t hash,
526+
PyDictEntry *ep, PyObject *value)
529527
{
530528
PyObject *old_value;
531-
register PyDictEntry *ep;
532-
typedef PyDictEntry *(*lookupfunc)(PyDictObject *, PyObject *, Py_hash_t);
533529

534-
assert(mp->ma_lookup != NULL);
535-
ep = mp->ma_lookup(mp, key, hash);
536-
if (ep == NULL) {
537-
Py_DECREF(key);
538-
Py_DECREF(value);
539-
return -1;
540-
}
541530
MAINTAIN_TRACKING(mp, key, value);
542531
if (ep->me_value != NULL) {
543532
old_value = ep->me_value;
@@ -560,6 +549,28 @@ insertdict(register PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *v
560549
return 0;
561550
}
562551

552+
553+
/*
554+
Internal routine to insert a new item into the table.
555+
Used both by the internal resize routine and by the public insert routine.
556+
Eats a reference to key and one to value.
557+
Returns -1 if an error occurred, or 0 on success.
558+
*/
559+
static int
560+
insertdict(register PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
561+
{
562+
register PyDictEntry *ep;
563+
564+
assert(mp->ma_lookup != NULL);
565+
ep = mp->ma_lookup(mp, key, hash);
566+
if (ep == NULL) {
567+
Py_DECREF(key);
568+
Py_DECREF(value);
569+
return -1;
570+
}
571+
return insertdict_by_entry(mp, key, hash, ep, value);
572+
}
573+
563574
/*
564575
Internal routine used by dictresize() to insert an item which is
565576
known to be absent from the dict. This routine also assumes that
@@ -783,39 +794,26 @@ PyDict_GetItemWithError(PyObject *op, PyObject *key)
783794
return ep->me_value;
784795
}
785796

786-
/* CAUTION: PyDict_SetItem() must guarantee that it won't resize the
787-
* dictionary if it's merely replacing the value for an existing key.
788-
* This means that it's safe to loop over a dictionary with PyDict_Next()
789-
* and occasionally replace a value -- but you can't insert new keys or
790-
* remove them.
791-
*/
792-
int
793-
PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value)
797+
static int
798+
dict_set_item_by_hash_or_entry(register PyObject *op, PyObject *key,
799+
Py_hash_t hash, PyDictEntry *ep, PyObject *value)
794800
{
795801
register PyDictObject *mp;
796-
register Py_hash_t hash;
797802
register Py_ssize_t n_used;
798803

799-
if (!PyDict_Check(op)) {
800-
PyErr_BadInternalCall();
801-
return -1;
802-
}
803-
assert(key);
804-
assert(value);
805804
mp = (PyDictObject *)op;
806-
if (!PyUnicode_CheckExact(key) ||
807-
(hash = ((PyASCIIObject *) key)->hash) == -1)
808-
{
809-
hash = PyObject_Hash(key);
810-
if (hash == -1)
811-
return -1;
812-
}
813805
assert(mp->ma_fill <= mp->ma_mask); /* at least one empty slot */
814806
n_used = mp->ma_used;
815807
Py_INCREF(value);
816808
Py_INCREF(key);
817-
if (insertdict(mp, key, hash, value) != 0)
818-
return -1;
809+
if (ep == NULL) {
810+
if (insertdict(mp, key, hash, value) != 0)
811+
return -1;
812+
}
813+
else {
814+
if (insertdict_by_entry(mp, key, hash, ep, value) != 0)
815+
return -1;
816+
}
819817
/* If we added a key, we can safely resize. Otherwise just return!
820818
* If fill >= 2/3 size, adjust size. Normally, this doubles or
821819
* quaduples the size, but it's also possible for the dict to shrink
@@ -835,6 +833,36 @@ PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value)
835833
return dictresize(mp, (mp->ma_used > 50000 ? 2 : 4) * mp->ma_used);
836834
}
837835

836+
/* CAUTION: PyDict_SetItem() must guarantee that it won't resize the
837+
* dictionary if it's merely replacing the value for an existing key.
838+
* This means that it's safe to loop over a dictionary with PyDict_Next()
839+
* and occasionally replace a value -- but you can't insert new keys or
840+
* remove them.
841+
*/
842+
int
843+
PyDict_SetItem(register PyObject *op, PyObject *key, PyObject *value)
844+
{
845+
register Py_hash_t hash;
846+
847+
if (!PyDict_Check(op)) {
848+
PyErr_BadInternalCall();
849+
return -1;
850+
}
851+
assert(key);
852+
assert(value);
853+
if (PyUnicode_CheckExact(key)) {
854+
hash = ((PyASCIIObject *) key)->hash;
855+
if (hash == -1)
856+
hash = PyObject_Hash(key);
857+
}
858+
else {
859+
hash = PyObject_Hash(key);
860+
if (hash == -1)
861+
return -1;
862+
}
863+
return dict_set_item_by_hash_or_entry(op, key, hash, NULL, value);
864+
}
865+
838866
int
839867
PyDict_DelItem(PyObject *op, PyObject *key)
840868
{
@@ -1803,9 +1831,9 @@ dict_setdefault(register PyDictObject *mp, PyObject *args)
18031831
return NULL;
18041832
val = ep->me_value;
18051833
if (val == NULL) {
1806-
val = failobj;
1807-
if (PyDict_SetItem((PyObject*)mp, key, failobj))
1808-
val = NULL;
1834+
if (dict_set_item_by_hash_or_entry((PyObject*)mp, key, hash, ep,
1835+
failobj) == 0)
1836+
val = failobj;
18091837
}
18101838
Py_XINCREF(val);
18111839
return val;

0 commit comments

Comments
 (0)