Skip to content
10 changes: 10 additions & 0 deletions Lib/test/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,16 @@ def test_dictview_set_operations_on_items(self):
self.assertEqual(k1 ^ k2, {(3,3)})
self.assertEqual(k1 ^ k3, {(1,1), (2,2), (4,4)})

def test_items_symmetric_difference(self):
rr = random.randrange
for _ in range(100):
left = {x:rr(3) for x in range(20) if rr(2)}
right = {x:rr(3) for x in range(20) if rr(2)}
with self.subTest(left=left, right=right):
expected = set(left.items()) ^ set(right.items())
actual = left.items() ^ right.items()
self.assertEqual(actual, expected)

def test_dictview_mixed_set_operations(self):
# Just a few for .keys()
self.assertTrue({1:1}.keys() == {1})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved the performance of symmetric difference operations on dictionary item views. Patch by Dennis Sweeney.
90 changes: 90 additions & 0 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4409,9 +4409,99 @@ dictviews_or(PyObject* self, PyObject *other)
return result;
}

static PyObject *
dictitems_xor(PyObject *self, PyObject *other)
{
assert(PyDictItems_Check(self));
assert(PyDictItems_Check(other));
PyObject *d1 = (PyObject *)((_PyDictViewObject *)self)->dv_dict;
PyObject *d2 = (PyObject *)((_PyDictViewObject *)other)->dv_dict;

PyObject *temp_dict = PyDict_Copy(d1);
if (temp_dict == NULL) {
return NULL;
}
PyObject *result_set = PySet_New(NULL);
if (result_set == NULL) {
Py_CLEAR(temp_dict);
return NULL;
}

PyObject *key = NULL, *val1 = NULL, *val2 = NULL;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally expect these declarations to go at the top of the function (it's not required but it is the usual style in our C code).

Py_ssize_t pos = 0;
Py_hash_t hash;

while (_PyDict_Next(d2, &pos, &key, &val2, &hash)) {
Py_INCREF(key);
Py_INCREF(val2);
val1 = _PyDict_GetItem_KnownHash(temp_dict, key, hash);

int to_delete;
if (val1 == NULL) {
if (PyErr_Occurred()) {
goto error;
}
to_delete = 0;
}
else {
Py_INCREF(val1);
to_delete = PyObject_RichCompareBool(val1, val2, Py_EQ);
if (to_delete < 0) {
goto error;
}
}

if (to_delete) {
if (_PyDict_DelItem_KnownHash(temp_dict, key, hash) < 0) {
goto error;
}
}
else {
PyObject *pair = PyTuple_Pack(2, key, val2);
if (pair == NULL) {
goto error;
}
if (PySet_Add(result_set, pair) < 0) {
Py_DECREF(pair);
goto error;
}
Py_DECREF(pair);
}
Py_DECREF(key);
Py_XDECREF(val1);
Py_DECREF(val2);
}
key = val1 = val2 = NULL;

_Py_IDENTIFIER(items);
PyObject *remaining_pairs = _PyObject_CallMethodIdNoArgs(temp_dict,
&PyId_items);
if (remaining_pairs == NULL) {
goto error;
}
if (_PySet_Update(result_set, remaining_pairs) < 0) {
Py_DECREF(remaining_pairs);
goto error;
}
Py_DECREF(temp_dict);
Py_DECREF(remaining_pairs);
return result_set;

error:
Py_XDECREF(temp_dict);
Py_XDECREF(result_set);
Py_XDECREF(key);
Py_XDECREF(val1);
Py_XDECREF(val2);
return NULL;
}

static PyObject*
dictviews_xor(PyObject* self, PyObject *other)
{
if (PyDictItems_Check(self) && PyDictItems_Check(other)) {
return dictitems_xor(self, other);
}
PyObject *result = dictviews_to_set(self);
if (result == NULL) {
return NULL;
Expand Down