Skip to content

Commit eb8ac57

Browse files
authored
bpo-36144: Dictionary Union (PEP 584) (#12088)
1 parent ba22e8f commit eb8ac57

File tree

4 files changed

+107
-18
lines changed

4 files changed

+107
-18
lines changed

Lib/collections/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,26 @@ def __contains__(self, key):
994994

995995
# Now, add the methods in dicts but not in MutableMapping
996996
def __repr__(self): return repr(self.data)
997+
998+
def __or__(self, other):
999+
if isinstance(other, UserDict):
1000+
return self.__class__(self.data | other.data)
1001+
if isinstance(other, dict):
1002+
return self.__class__(self.data | other)
1003+
return NotImplemented
1004+
def __ror__(self, other):
1005+
if isinstance(other, UserDict):
1006+
return self.__class__(other.data | self.data)
1007+
if isinstance(other, dict):
1008+
return self.__class__(other | self.data)
1009+
return NotImplemented
1010+
def __ior__(self, other):
1011+
if isinstance(other, UserDict):
1012+
self.data |= other.data
1013+
else:
1014+
self.data |= other
1015+
return self
1016+
9971017
def __copy__(self):
9981018
inst = self.__class__.__new__(self.__class__)
9991019
inst.__dict__.update(self.__dict__)

Lib/test/test_dict.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,38 @@ def test_literal_constructor(self):
3737
dictliteral = '{' + ', '.join(formatted_items) + '}'
3838
self.assertEqual(eval(dictliteral), dict(items))
3939

40+
def test_merge_operator(self):
41+
42+
a = {0: 0, 1: 1, 2: 1}
43+
b = {1: 1, 2: 2, 3: 3}
44+
45+
c = a.copy()
46+
c |= b
47+
48+
self.assertEqual(a | b, {0: 0, 1: 1, 2: 2, 3: 3})
49+
self.assertEqual(c, {0: 0, 1: 1, 2: 2, 3: 3})
50+
51+
c = b.copy()
52+
c |= a
53+
54+
self.assertEqual(b | a, {1: 1, 2: 1, 3: 3, 0: 0})
55+
self.assertEqual(c, {1: 1, 2: 1, 3: 3, 0: 0})
56+
57+
c = a.copy()
58+
c |= [(1, 1), (2, 2), (3, 3)]
59+
60+
self.assertEqual(c, {0: 0, 1: 1, 2: 2, 3: 3})
61+
62+
self.assertIs(a.__or__(None), NotImplemented)
63+
self.assertIs(a.__or__(()), NotImplemented)
64+
self.assertIs(a.__or__("BAD"), NotImplemented)
65+
self.assertIs(a.__or__(""), NotImplemented)
66+
67+
self.assertRaises(TypeError, a.__ior__, None)
68+
self.assertEqual(a.__ior__(()), {0: 0, 1: 1, 2: 1})
69+
self.assertRaises(ValueError, a.__ior__, "BAD")
70+
self.assertEqual(a.__ior__(""), {0: 0, 1: 1, 2: 1})
71+
4072
def test_bool(self):
4173
self.assertIs(not {}, True)
4274
self.assertTrue({1: 2})
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`dict` (and :class:`collections.UserDict`) objects now support PEP 584's merge (``|``) and update (``|=``) operators.
2+
Patch by Brandt Bucher.

Objects/dictobject.c

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2320,6 +2320,25 @@ dict_fromkeys_impl(PyTypeObject *type, PyObject *iterable, PyObject *value)
23202320
return _PyDict_FromKeys((PyObject *)type, iterable, value);
23212321
}
23222322

2323+
/* Single-arg dict update; used by dict_update_common and operators. */
2324+
static int
2325+
dict_update_arg(PyObject *self, PyObject *arg)
2326+
{
2327+
if (PyDict_CheckExact(arg)) {
2328+
return PyDict_Merge(self, arg, 1);
2329+
}
2330+
_Py_IDENTIFIER(keys);
2331+
PyObject *func;
2332+
if (_PyObject_LookupAttrId(arg, &PyId_keys, &func) < 0) {
2333+
return -1;
2334+
}
2335+
if (func != NULL) {
2336+
Py_DECREF(func);
2337+
return PyDict_Merge(self, arg, 1);
2338+
}
2339+
return PyDict_MergeFromSeq2(self, arg, 1);
2340+
}
2341+
23232342
static int
23242343
dict_update_common(PyObject *self, PyObject *args, PyObject *kwds,
23252344
const char *methname)
@@ -2331,23 +2350,7 @@ dict_update_common(PyObject *self, PyObject *args, PyObject *kwds,
23312350
result = -1;
23322351
}
23332352
else if (arg != NULL) {
2334-
if (PyDict_CheckExact(arg)) {
2335-
result = PyDict_Merge(self, arg, 1);
2336-
}
2337-
else {
2338-
_Py_IDENTIFIER(keys);
2339-
PyObject *func;
2340-
if (_PyObject_LookupAttrId(arg, &PyId_keys, &func) < 0) {
2341-
result = -1;
2342-
}
2343-
else if (func != NULL) {
2344-
Py_DECREF(func);
2345-
result = PyDict_Merge(self, arg, 1);
2346-
}
2347-
else {
2348-
result = PyDict_MergeFromSeq2(self, arg, 1);
2349-
}
2350-
}
2353+
result = dict_update_arg(self, arg);
23512354
}
23522355

23532356
if (result == 0 && kwds != NULL) {
@@ -3169,6 +3172,33 @@ dict_sizeof(PyDictObject *mp, PyObject *Py_UNUSED(ignored))
31693172
return PyLong_FromSsize_t(_PyDict_SizeOf(mp));
31703173
}
31713174

3175+
static PyObject *
3176+
dict_or(PyObject *self, PyObject *other)
3177+
{
3178+
if (!PyDict_Check(self) || !PyDict_Check(other)) {
3179+
Py_RETURN_NOTIMPLEMENTED;
3180+
}
3181+
PyObject *new = PyDict_Copy(self);
3182+
if (new == NULL) {
3183+
return NULL;
3184+
}
3185+
if (dict_update_arg(new, other)) {
3186+
Py_DECREF(new);
3187+
return NULL;
3188+
}
3189+
return new;
3190+
}
3191+
3192+
static PyObject *
3193+
dict_ior(PyObject *self, PyObject *other)
3194+
{
3195+
if (dict_update_arg(self, other)) {
3196+
return NULL;
3197+
}
3198+
Py_INCREF(self);
3199+
return self;
3200+
}
3201+
31723202
PyDoc_STRVAR(getitem__doc__, "x.__getitem__(y) <==> x[y]");
31733203

31743204
PyDoc_STRVAR(sizeof__doc__,
@@ -3274,6 +3304,11 @@ static PySequenceMethods dict_as_sequence = {
32743304
0, /* sq_inplace_repeat */
32753305
};
32763306

3307+
static PyNumberMethods dict_as_number = {
3308+
.nb_or = dict_or,
3309+
.nb_inplace_or = dict_ior,
3310+
};
3311+
32773312
static PyObject *
32783313
dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
32793314
{
@@ -3335,7 +3370,7 @@ PyTypeObject PyDict_Type = {
33353370
0, /* tp_setattr */
33363371
0, /* tp_as_async */
33373372
(reprfunc)dict_repr, /* tp_repr */
3338-
0, /* tp_as_number */
3373+
&dict_as_number, /* tp_as_number */
33393374
&dict_as_sequence, /* tp_as_sequence */
33403375
&dict_as_mapping, /* tp_as_mapping */
33413376
PyObject_HashNotImplemented, /* tp_hash */

0 commit comments

Comments
 (0)