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

Skip to content

Commit e63415e

Browse files
committed
SF patch #421922: Implement rich comparison for dicts.
d1 == d2 and d1 != d2 now work even if the keys and values in d1 and d2 don't support comparisons other than ==, and testing dicts for equality is faster now (especially when inequality obtains).
1 parent 66a7e57 commit e63415e

3 files changed

Lines changed: 104 additions & 4 deletions

File tree

Lib/test/test_richcmp.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,33 @@ def check(s, a=a, b=b):
221221
check('not a==b')
222222
if verbose: print "recursion tests ok"
223223

224+
def dicts():
225+
# Verify that __eq__ and __ne__ work for dicts even if the keys and
226+
# values don't support anything other than __eq__ and __ne__. Complex
227+
# numbers are a fine example of that.
228+
import random
229+
imag1a = {}
230+
for i in range(50):
231+
imag1a[random.randrange(100)*1j] = random.randrange(100)*1j
232+
items = imag1a.items()
233+
random.shuffle(items)
234+
imag1b = {}
235+
for k, v in items:
236+
imag1b[k] = v
237+
imag2 = imag1b.copy()
238+
imag2[k] = v + 1.0
239+
verify(imag1a == imag1a, "imag1a == imag1a should have worked")
240+
verify(imag1a == imag1b, "imag1a == imag1b should have worked")
241+
verify(imag2 == imag2, "imag2 == imag2 should have worked")
242+
verify(imag1a != imag2, "imag1a != imag2 should have worked")
243+
for op in "<", "<=", ">", ">=":
244+
try:
245+
eval("imag1a %s imag2" % op)
246+
except TypeError:
247+
pass
248+
else:
249+
raise TestFailed("expected TypeError from imag1a %s imag2" % op)
250+
224251
def main():
225252
basic()
226253
tabulate()
@@ -229,5 +256,6 @@ def main():
229256
testvector()
230257
misbehavin()
231258
recursion()
259+
dicts()
232260

233261
main()

Misc/NEWS

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ Core
1717

1818
- The following functions were generalized to work nicely with iterator
1919
arguments:
20-
map(), filter(), reduce()
20+
map(), filter(), reduce(), zip()
2121
list(), tuple() (PySequence_Tuple() and PySequence_Fast() in C API)
2222
max(), min()
23-
zip()
2423
.join() method of strings
2524
'x in y' and 'x not in y' (PySequence_Contains() in C API)
2625
operator.countOf() (PySequence_Count() in C API)
2726

27+
- Comparing dictionary objects via == and != is faster, and now works even
28+
if the keys and values don't support comparisons other than ==.
29+
2830

2931
What's New in Python 2.1 (final)?
3032
=================================

Objects/dictobject.c

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,76 @@ dict_compare(dictobject *a, dictobject *b)
10471047
return res;
10481048
}
10491049

1050+
/* Return 1 if dicts equal, 0 if not, -1 if error.
1051+
* Gets out as soon as any difference is detected.
1052+
* Uses only Py_EQ comparison.
1053+
*/
1054+
static int
1055+
dict_equal(dictobject *a, dictobject *b)
1056+
{
1057+
int i;
1058+
1059+
if (a->ma_used != b->ma_used)
1060+
/* can't be equal if # of entries differ */
1061+
return 0;
1062+
1063+
/* Same # of entries -- check all of 'em. Exit early on any diff. */
1064+
for (i = 0; i < a->ma_size; i++) {
1065+
PyObject *aval = a->ma_table[i].me_value;
1066+
if (aval != NULL) {
1067+
int cmp;
1068+
PyObject *bval;
1069+
PyObject *key = a->ma_table[i].me_key;
1070+
/* temporarily bump aval's refcount to ensure it stays
1071+
alive until we're done with it */
1072+
Py_INCREF(aval);
1073+
bval = PyDict_GetItem((PyObject *)b, key);
1074+
if (bval == NULL) {
1075+
Py_DECREF(aval);
1076+
return 0;
1077+
}
1078+
cmp = PyObject_RichCompareBool(aval, bval, Py_EQ);
1079+
Py_DECREF(aval);
1080+
if (cmp <= 0) /* error or not equal */
1081+
return cmp;
1082+
}
1083+
}
1084+
return 1;
1085+
}
1086+
1087+
static PyObject *
1088+
dict_richcompare(PyObject *v, PyObject *w, int op)
1089+
{
1090+
int cmp;
1091+
PyObject *res;
1092+
1093+
if (!PyDict_Check(v) || !PyDict_Check(w)) {
1094+
res = Py_NotImplemented;
1095+
}
1096+
else if (op == Py_EQ || op == Py_NE) {
1097+
cmp = dict_equal((dictobject *)v, (dictobject *)w);
1098+
if (cmp < 0)
1099+
return NULL;
1100+
res = (cmp == (op == Py_EQ)) ? Py_True : Py_False;
1101+
}
1102+
else {
1103+
cmp = dict_compare((dictobject *)v, (dictobject *)w);
1104+
if (cmp < 0 && PyErr_Occurred())
1105+
return NULL;
1106+
switch (op) {
1107+
case Py_LT: cmp = cmp < 0; break;
1108+
case Py_LE: cmp = cmp <= 0; break;
1109+
case Py_GT: cmp = cmp > 0; break;
1110+
case Py_GE: cmp = cmp >= 0; break;
1111+
default:
1112+
assert(!"op unexpected");
1113+
}
1114+
res = cmp ? Py_True : Py_False;
1115+
}
1116+
Py_INCREF(res);
1117+
return res;
1118+
}
1119+
10501120
static PyObject *
10511121
dict_has_key(register dictobject *mp, PyObject *args)
10521122
{
@@ -1410,7 +1480,7 @@ PyTypeObject PyDict_Type = {
14101480
(printfunc)dict_print, /* tp_print */
14111481
(getattrfunc)dict_getattr, /* tp_getattr */
14121482
0, /* tp_setattr */
1413-
(cmpfunc)dict_compare, /* tp_compare */
1483+
0, /* tp_compare */
14141484
(reprfunc)dict_repr, /* tp_repr */
14151485
0, /* tp_as_number */
14161486
&dict_as_sequence, /* tp_as_sequence */
@@ -1425,7 +1495,7 @@ PyTypeObject PyDict_Type = {
14251495
0, /* tp_doc */
14261496
(traverseproc)dict_traverse, /* tp_traverse */
14271497
(inquiry)dict_tp_clear, /* tp_clear */
1428-
0, /* tp_richcompare */
1498+
dict_richcompare, /* tp_richcompare */
14291499
0, /* tp_weaklistoffset */
14301500
(getiterfunc)dict_iter, /* tp_iter */
14311501
0, /* tp_iternext */

0 commit comments

Comments
 (0)