Skip to content

Commit 6d674a1

Browse files
authored
bpo-36144: OrderedDict Union (PEP 584) (#18967)
1 parent d648ef1 commit 6d674a1

File tree

5 files changed

+187
-75
lines changed

5 files changed

+187
-75
lines changed

Doc/library/collections.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,10 @@ anywhere a regular dictionary is used.
11231123
passed to the :class:`OrderedDict` constructor and its :meth:`update`
11241124
method.
11251125

1126+
.. versionchanged:: 3.9
1127+
Added merge (``|``) and update (``|=``) operators, specified in :pep:`584`.
1128+
1129+
11261130
:class:`OrderedDict` Examples and Recipes
11271131
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11281132

Lib/collections/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,24 @@ def __eq__(self, other):
293293
return dict.__eq__(self, other) and all(map(_eq, self, other))
294294
return dict.__eq__(self, other)
295295

296+
def __ior__(self, other):
297+
self.update(other)
298+
return self
299+
300+
def __or__(self, other):
301+
if not isinstance(other, dict):
302+
return NotImplemented
303+
new = self.__class__(self)
304+
new.update(other)
305+
return new
306+
307+
def __ror__(self, other):
308+
if not isinstance(other, dict):
309+
return NotImplemented
310+
new = self.__class__(other)
311+
new.update(self)
312+
return new
313+
296314

297315
try:
298316
from _collections import OrderedDict

Lib/test/test_ordered_dict.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,49 @@ def test_free_after_iterating(self):
654654
support.check_free_after_iterating(self, lambda d: iter(d.values()), self.OrderedDict)
655655
support.check_free_after_iterating(self, lambda d: iter(d.items()), self.OrderedDict)
656656

657+
def test_merge_operator(self):
658+
OrderedDict = self.OrderedDict
659+
660+
a = OrderedDict({0: 0, 1: 1, 2: 1})
661+
b = OrderedDict({1: 1, 2: 2, 3: 3})
662+
663+
c = a.copy()
664+
d = a.copy()
665+
c |= b
666+
d |= list(b.items())
667+
expected = OrderedDict({0: 0, 1: 1, 2: 2, 3: 3})
668+
self.assertEqual(a | dict(b), expected)
669+
self.assertEqual(a | b, expected)
670+
self.assertEqual(c, expected)
671+
self.assertEqual(d, expected)
672+
673+
c = b.copy()
674+
c |= a
675+
expected = OrderedDict({1: 1, 2: 1, 3: 3, 0: 0})
676+
self.assertEqual(dict(b) | a, expected)
677+
self.assertEqual(b | a, expected)
678+
self.assertEqual(c, expected)
679+
680+
self.assertIs(type(a | b), OrderedDict)
681+
self.assertIs(type(dict(a) | b), OrderedDict)
682+
self.assertIs(type(a | dict(b)), OrderedDict)
683+
684+
expected = a.copy()
685+
a |= ()
686+
a |= ""
687+
self.assertEqual(a, expected)
688+
689+
with self.assertRaises(TypeError):
690+
a | None
691+
with self.assertRaises(TypeError):
692+
a | ()
693+
with self.assertRaises(TypeError):
694+
a | "BAD"
695+
with self.assertRaises(TypeError):
696+
a | ""
697+
with self.assertRaises(ValueError):
698+
a |= "BAD"
699+
657700

658701
class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase):
659702

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:class:`collections.OrderedDict` now implements ``|`` and ``|=``
2+
(:pep:`584`).

Objects/odictobject.c

Lines changed: 120 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,57 @@ static PyMappingMethods odict_as_mapping = {
851851
};
852852

853853

854+
/* ----------------------------------------------
855+
* OrderedDict number methods
856+
*/
857+
858+
static int mutablemapping_update_arg(PyObject*, PyObject*);
859+
860+
static PyObject *
861+
odict_or(PyObject *left, PyObject *right)
862+
{
863+
PyTypeObject *type;
864+
PyObject *other;
865+
if (PyODict_Check(left)) {
866+
type = Py_TYPE(left);
867+
other = right;
868+
}
869+
else {
870+
type = Py_TYPE(right);
871+
other = left;
872+
}
873+
if (!PyDict_Check(other)) {
874+
Py_RETURN_NOTIMPLEMENTED;
875+
}
876+
PyObject *new = PyObject_CallOneArg((PyObject*)type, left);
877+
if (!new) {
878+
return NULL;
879+
}
880+
if (mutablemapping_update_arg(new, right) < 0) {
881+
Py_DECREF(new);
882+
return NULL;
883+
}
884+
return new;
885+
}
886+
887+
static PyObject *
888+
odict_inplace_or(PyObject *self, PyObject *other)
889+
{
890+
if (mutablemapping_update_arg(self, other) < 0) {
891+
return NULL;
892+
}
893+
Py_INCREF(self);
894+
return self;
895+
}
896+
897+
/* tp_as_number */
898+
899+
static PyNumberMethods odict_as_number = {
900+
.nb_or = odict_or,
901+
.nb_inplace_or = odict_inplace_or,
902+
};
903+
904+
854905
/* ----------------------------------------------
855906
* OrderedDict methods
856907
*/
@@ -1555,7 +1606,7 @@ PyTypeObject PyODict_Type = {
15551606
0, /* tp_setattr */
15561607
0, /* tp_as_async */
15571608
(reprfunc)odict_repr, /* tp_repr */
1558-
0, /* tp_as_number */
1609+
&odict_as_number, /* tp_as_number */
15591610
0, /* tp_as_sequence */
15601611
&odict_as_mapping, /* tp_as_mapping */
15611612
0, /* tp_hash */
@@ -2189,100 +2240,94 @@ mutablemapping_add_pairs(PyObject *self, PyObject *pairs)
21892240
return 0;
21902241
}
21912242

2192-
static PyObject *
2193-
mutablemapping_update(PyObject *self, PyObject *args, PyObject *kwargs)
2243+
static int
2244+
mutablemapping_update_arg(PyObject *self, PyObject *arg)
21942245
{
21952246
int res = 0;
2196-
Py_ssize_t len;
2247+
if (PyDict_CheckExact(arg)) {
2248+
PyObject *items = PyDict_Items(arg);
2249+
if (items == NULL) {
2250+
return -1;
2251+
}
2252+
res = mutablemapping_add_pairs(self, items);
2253+
Py_DECREF(items);
2254+
return res;
2255+
}
21972256
_Py_IDENTIFIER(keys);
2257+
PyObject *func;
2258+
if (_PyObject_LookupAttrId(arg, &PyId_keys, &func) < 0) {
2259+
return -1;
2260+
}
2261+
if (func != NULL) {
2262+
PyObject *keys = _PyObject_CallNoArg(func);
2263+
Py_DECREF(func);
2264+
if (keys == NULL) {
2265+
return -1;
2266+
}
2267+
PyObject *iterator = PyObject_GetIter(keys);
2268+
Py_DECREF(keys);
2269+
if (iterator == NULL) {
2270+
return -1;
2271+
}
2272+
PyObject *key;
2273+
while (res == 0 && (key = PyIter_Next(iterator))) {
2274+
PyObject *value = PyObject_GetItem(arg, key);
2275+
if (value != NULL) {
2276+
res = PyObject_SetItem(self, key, value);
2277+
Py_DECREF(value);
2278+
}
2279+
else {
2280+
res = -1;
2281+
}
2282+
Py_DECREF(key);
2283+
}
2284+
Py_DECREF(iterator);
2285+
if (res != 0 || PyErr_Occurred()) {
2286+
return -1;
2287+
}
2288+
return 0;
2289+
}
2290+
if (_PyObject_LookupAttrId(arg, &PyId_items, &func) < 0) {
2291+
return -1;
2292+
}
2293+
if (func != NULL) {
2294+
PyObject *items = _PyObject_CallNoArg(func);
2295+
Py_DECREF(func);
2296+
if (items == NULL) {
2297+
return -1;
2298+
}
2299+
res = mutablemapping_add_pairs(self, items);
2300+
Py_DECREF(items);
2301+
return res;
2302+
}
2303+
res = mutablemapping_add_pairs(self, arg);
2304+
return res;
2305+
}
21982306

2307+
static PyObject *
2308+
mutablemapping_update(PyObject *self, PyObject *args, PyObject *kwargs)
2309+
{
2310+
int res;
21992311
/* first handle args, if any */
22002312
assert(args == NULL || PyTuple_Check(args));
2201-
len = (args != NULL) ? PyTuple_GET_SIZE(args) : 0;
2313+
Py_ssize_t len = (args != NULL) ? PyTuple_GET_SIZE(args) : 0;
22022314
if (len > 1) {
22032315
const char *msg = "update() takes at most 1 positional argument (%zd given)";
22042316
PyErr_Format(PyExc_TypeError, msg, len);
22052317
return NULL;
22062318
}
22072319

22082320
if (len) {
2209-
PyObject *func;
22102321
PyObject *other = PyTuple_GET_ITEM(args, 0); /* borrowed reference */
22112322
assert(other != NULL);
22122323
Py_INCREF(other);
2213-
if (PyDict_CheckExact(other)) {
2214-
PyObject *items = PyDict_Items(other);
2215-
Py_DECREF(other);
2216-
if (items == NULL)
2217-
return NULL;
2218-
res = mutablemapping_add_pairs(self, items);
2219-
Py_DECREF(items);
2220-
if (res == -1)
2221-
return NULL;
2222-
goto handle_kwargs;
2223-
}
2224-
2225-
if (_PyObject_LookupAttrId(other, &PyId_keys, &func) < 0) {
2226-
Py_DECREF(other);
2227-
return NULL;
2228-
}
2229-
if (func != NULL) {
2230-
PyObject *keys, *iterator, *key;
2231-
keys = _PyObject_CallNoArg(func);
2232-
Py_DECREF(func);
2233-
if (keys == NULL) {
2234-
Py_DECREF(other);
2235-
return NULL;
2236-
}
2237-
iterator = PyObject_GetIter(keys);
2238-
Py_DECREF(keys);
2239-
if (iterator == NULL) {
2240-
Py_DECREF(other);
2241-
return NULL;
2242-
}
2243-
while (res == 0 && (key = PyIter_Next(iterator))) {
2244-
PyObject *value = PyObject_GetItem(other, key);
2245-
if (value != NULL) {
2246-
res = PyObject_SetItem(self, key, value);
2247-
Py_DECREF(value);
2248-
}
2249-
else {
2250-
res = -1;
2251-
}
2252-
Py_DECREF(key);
2253-
}
2254-
Py_DECREF(other);
2255-
Py_DECREF(iterator);
2256-
if (res != 0 || PyErr_Occurred())
2257-
return NULL;
2258-
goto handle_kwargs;
2259-
}
2260-
2261-
if (_PyObject_LookupAttrId(other, &PyId_items, &func) < 0) {
2262-
Py_DECREF(other);
2263-
return NULL;
2264-
}
2265-
if (func != NULL) {
2266-
PyObject *items;
2267-
Py_DECREF(other);
2268-
items = _PyObject_CallNoArg(func);
2269-
Py_DECREF(func);
2270-
if (items == NULL)
2271-
return NULL;
2272-
res = mutablemapping_add_pairs(self, items);
2273-
Py_DECREF(items);
2274-
if (res == -1)
2275-
return NULL;
2276-
goto handle_kwargs;
2277-
}
2278-
2279-
res = mutablemapping_add_pairs(self, other);
2324+
res = mutablemapping_update_arg(self, other);
22802325
Py_DECREF(other);
2281-
if (res != 0)
2326+
if (res < 0) {
22822327
return NULL;
2328+
}
22832329
}
22842330

2285-
handle_kwargs:
22862331
/* now handle kwargs */
22872332
assert(kwargs == NULL || PyDict_Check(kwargs));
22882333
if (kwargs != NULL && PyDict_GET_SIZE(kwargs)) {

0 commit comments

Comments
 (0)