Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Doc/library/collections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,10 @@ anywhere a regular dictionary is used.
passed to the :class:`OrderedDict` constructor and its :meth:`update`
method.

.. versionchanged:: 3.9
Added merge (``|``) and update (``|=``) operators, specified in :pep:`584`.


:class:`OrderedDict` Examples and Recipes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
18 changes: 18 additions & 0 deletions Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,24 @@ def __eq__(self, other):
return dict.__eq__(self, other) and all(map(_eq, self, other))
return dict.__eq__(self, other)

def __ior__(self, other):
self.update(other)
return self

def __or__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(self)
new.update(other)
return new

def __ror__(self, other):
if not isinstance(other, dict):
return NotImplemented
new = self.__class__(other)
new.update(self)
return new


try:
from _collections import OrderedDict
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_ordered_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,49 @@ def test_free_after_iterating(self):
support.check_free_after_iterating(self, lambda d: iter(d.values()), self.OrderedDict)
support.check_free_after_iterating(self, lambda d: iter(d.items()), self.OrderedDict)

def test_merge_operator(self):
OrderedDict = self.OrderedDict

a = OrderedDict({0: 0, 1: 1, 2: 1})
b = OrderedDict({1: 1, 2: 2, 3: 3})

c = a.copy()
d = a.copy()
c |= b
d |= list(b.items())
expected = OrderedDict({0: 0, 1: 1, 2: 2, 3: 3})
self.assertEqual(a | dict(b), expected)
self.assertEqual(a | b, expected)
self.assertEqual(c, expected)
self.assertEqual(d, expected)

c = b.copy()
c |= a
expected = OrderedDict({1: 1, 2: 1, 3: 3, 0: 0})
self.assertEqual(dict(b) | a, expected)
self.assertEqual(b | a, expected)
self.assertEqual(c, expected)

self.assertIs(type(a | b), OrderedDict)
self.assertIs(type(dict(a) | b), OrderedDict)
self.assertIs(type(a | dict(b)), OrderedDict)

expected = a.copy()
a |= ()
a |= ""
self.assertEqual(a, expected)

with self.assertRaises(TypeError):
a | None
with self.assertRaises(TypeError):
a | ()
with self.assertRaises(TypeError):
a | "BAD"
with self.assertRaises(TypeError):
a | ""
with self.assertRaises(ValueError):
a |= "BAD"


class PurePythonOrderedDictTests(OrderedDictTests, unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:class:`collections.OrderedDict` now implements ``|`` and ``|=``
(:pep:`584`).
195 changes: 120 additions & 75 deletions Objects/odictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,57 @@ static PyMappingMethods odict_as_mapping = {
};


/* ----------------------------------------------
* OrderedDict number methods
*/

static int mutablemapping_update_arg(PyObject*, PyObject*);

static PyObject *
odict_or(PyObject *left, PyObject *right)
{
PyTypeObject *type;
PyObject *other;
if (PyODict_Check(left)) {
type = Py_TYPE(left);
other = right;
}
else {
type = Py_TYPE(right);
other = left;
}
if (!PyDict_Check(other)) {
Py_RETURN_NOTIMPLEMENTED;
}
PyObject *new = PyObject_CallOneArg((PyObject*)type, left);
if (!new) {
return NULL;
}
if (mutablemapping_update_arg(new, right) < 0) {
Py_DECREF(new);
return NULL;
}
return new;
}

static PyObject *
odict_inplace_or(PyObject *self, PyObject *other)
{
if (mutablemapping_update_arg(self, other) < 0) {
return NULL;
}
Py_INCREF(self);
return self;
}

/* tp_as_number */

static PyNumberMethods odict_as_number = {
.nb_or = odict_or,
.nb_inplace_or = odict_inplace_or,
};


/* ----------------------------------------------
* OrderedDict methods
*/
Expand Down Expand Up @@ -1557,7 +1608,7 @@ PyTypeObject PyODict_Type = {
0, /* tp_setattr */
0, /* tp_as_async */
(reprfunc)odict_repr, /* tp_repr */
0, /* tp_as_number */
&odict_as_number, /* tp_as_number */
0, /* tp_as_sequence */
&odict_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
Expand Down Expand Up @@ -2191,100 +2242,94 @@ mutablemapping_add_pairs(PyObject *self, PyObject *pairs)
return 0;
}

static PyObject *
mutablemapping_update(PyObject *self, PyObject *args, PyObject *kwargs)
static int
mutablemapping_update_arg(PyObject *self, PyObject *arg)
{
int res = 0;
Py_ssize_t len;
if (PyDict_CheckExact(arg)) {
PyObject *items = PyDict_Items(arg);
if (items == NULL) {
return -1;
}
res = mutablemapping_add_pairs(self, items);
Py_DECREF(items);
return res;
}
_Py_IDENTIFIER(keys);
PyObject *func;
if (_PyObject_LookupAttrId(arg, &PyId_keys, &func) < 0) {
return -1;
}
if (func != NULL) {
PyObject *keys = _PyObject_CallNoArg(func);
Py_DECREF(func);
if (keys == NULL) {
return -1;
}
PyObject *iterator = PyObject_GetIter(keys);
Py_DECREF(keys);
if (iterator == NULL) {
return -1;
}
PyObject *key;
while (res == 0 && (key = PyIter_Next(iterator))) {
PyObject *value = PyObject_GetItem(arg, key);
if (value != NULL) {
res = PyObject_SetItem(self, key, value);
Py_DECREF(value);
}
else {
res = -1;
}
Py_DECREF(key);
}
Py_DECREF(iterator);
if (res != 0 || PyErr_Occurred()) {
return -1;
}
return 0;
}
if (_PyObject_LookupAttrId(arg, &PyId_items, &func) < 0) {
return -1;
}
if (func != NULL) {
PyObject *items = _PyObject_CallNoArg(func);
Py_DECREF(func);
if (items == NULL) {
return -1;
}
res = mutablemapping_add_pairs(self, items);
Py_DECREF(items);
return res;
}
res = mutablemapping_add_pairs(self, arg);
return res;
}

static PyObject *
mutablemapping_update(PyObject *self, PyObject *args, PyObject *kwargs)
{
int res;
/* first handle args, if any */
assert(args == NULL || PyTuple_Check(args));
len = (args != NULL) ? PyTuple_GET_SIZE(args) : 0;
Py_ssize_t len = (args != NULL) ? PyTuple_GET_SIZE(args) : 0;
if (len > 1) {
const char *msg = "update() takes at most 1 positional argument (%zd given)";
PyErr_Format(PyExc_TypeError, msg, len);
return NULL;
}

if (len) {
PyObject *func;
PyObject *other = PyTuple_GET_ITEM(args, 0); /* borrowed reference */
assert(other != NULL);
Py_INCREF(other);
if (PyDict_CheckExact(other)) {
PyObject *items = PyDict_Items(other);
Py_DECREF(other);
if (items == NULL)
return NULL;
res = mutablemapping_add_pairs(self, items);
Py_DECREF(items);
if (res == -1)
return NULL;
goto handle_kwargs;
}

if (_PyObject_LookupAttrId(other, &PyId_keys, &func) < 0) {
Py_DECREF(other);
return NULL;
}
if (func != NULL) {
PyObject *keys, *iterator, *key;
keys = _PyObject_CallNoArg(func);
Py_DECREF(func);
if (keys == NULL) {
Py_DECREF(other);
return NULL;
}
iterator = PyObject_GetIter(keys);
Py_DECREF(keys);
if (iterator == NULL) {
Py_DECREF(other);
return NULL;
}
while (res == 0 && (key = PyIter_Next(iterator))) {
PyObject *value = PyObject_GetItem(other, key);
if (value != NULL) {
res = PyObject_SetItem(self, key, value);
Py_DECREF(value);
}
else {
res = -1;
}
Py_DECREF(key);
}
Py_DECREF(other);
Py_DECREF(iterator);
if (res != 0 || PyErr_Occurred())
return NULL;
goto handle_kwargs;
}

if (_PyObject_LookupAttrId(other, &PyId_items, &func) < 0) {
Py_DECREF(other);
return NULL;
}
if (func != NULL) {
PyObject *items;
Py_DECREF(other);
items = _PyObject_CallNoArg(func);
Py_DECREF(func);
if (items == NULL)
return NULL;
res = mutablemapping_add_pairs(self, items);
Py_DECREF(items);
if (res == -1)
return NULL;
goto handle_kwargs;
}

res = mutablemapping_add_pairs(self, other);
res = mutablemapping_update_arg(self, other);
Py_DECREF(other);
if (res != 0)
if (res < 0) {
return NULL;
}
}

handle_kwargs:
/* now handle kwargs */
assert(kwargs == NULL || PyDict_Check(kwargs));
if (kwargs != NULL && PyDict_GET_SIZE(kwargs)) {
Expand Down