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 @@ -729,6 +729,10 @@ stack manipulations such as ``dup``, ``drop``, ``swap``, ``over``, ``pick``,
initialized from the first argument to the constructor, if present, or to
``None``, if absent.

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


:class:`defaultdict` Examples
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
38 changes: 38 additions & 0 deletions Lib/test/test_defaultdict.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,5 +183,43 @@ def test_pickling(self):
o = pickle.loads(s)
self.assertEqual(d, o)

def test_union(self):
Copy link
Member

Choose a reason for hiding this comment

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

I see no tests for ‘|=‘?

Copy link
Member Author

Choose a reason for hiding this comment

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

defaultdict inherits a perfectly fine |= from dict.

I can add tests for it here if you'd like, but the tests for dict already have the same coverage.

Copy link
Member

Choose a reason for hiding this comment

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

D'oh. Maybe add a comment to this effect?

i = defaultdict(int, {1: 1, 2: 2})
s = defaultdict(str, {0: "zero", 1: "one"})

i_s = i | s
self.assertIs(i_s.default_factory, int)
self.assertDictEqual(i_s, {1: "one", 2: 2, 0: "zero"})
self.assertEqual(list(i_s), [1, 2, 0])

s_i = s | i
self.assertIs(s_i.default_factory, str)
self.assertDictEqual(s_i, {0: "zero", 1: 1, 2: 2})
self.assertEqual(list(s_i), [0, 1, 2])

i_ds = i | dict(s)
self.assertIs(i_ds.default_factory, int)
self.assertDictEqual(i_ds, {1: "one", 2: 2, 0: "zero"})
self.assertEqual(list(i_ds), [1, 2, 0])

ds_i = dict(s) | i
self.assertIs(ds_i.default_factory, int)
self.assertDictEqual(ds_i, {0: "zero", 1: 1, 2: 2})
self.assertEqual(list(ds_i), [0, 1, 2])

with self.assertRaises(TypeError):
i | list(s.items())
with self.assertRaises(TypeError):
list(s.items()) | i

# We inherit a fine |= from dict, so just a few sanity checks here:
i |= list(s.items())
self.assertIs(i.default_factory, int)
self.assertDictEqual(i, {1: "one", 2: 2, 0: "zero"})
self.assertEqual(list(i), [1, 2, 0])

with self.assertRaises(TypeError):
i |= None

if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:class:`collections.defaultdict` now implements ``|`` (:pep:`584`).
51 changes: 45 additions & 6 deletions Modules/_collectionsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1990,6 +1990,13 @@ defdict_missing(defdictobject *dd, PyObject *key)
return value;
}

static inline PyObject*
new_defdict(defdictobject *dd, PyObject *arg)
{
return PyObject_CallFunctionObjArgs((PyObject*)Py_TYPE(dd),
dd->default_factory ? dd->default_factory : Py_None, arg, NULL);
}

PyDoc_STRVAR(defdict_copy_doc, "D.copy() -> a shallow copy of D.");

static PyObject *
Expand All @@ -1999,11 +2006,7 @@ defdict_copy(defdictobject *dd, PyObject *Py_UNUSED(ignored))
whose class constructor has the same signature. Subclasses that
define a different constructor signature must override copy().
*/

if (dd->default_factory == NULL)
return PyObject_CallFunctionObjArgs((PyObject*)Py_TYPE(dd), Py_None, dd, NULL);
return PyObject_CallFunctionObjArgs((PyObject*)Py_TYPE(dd),
dd->default_factory, dd, NULL);
return new_defdict(dd, (PyObject*)dd);
}

static PyObject *
Expand Down Expand Up @@ -2127,6 +2130,42 @@ defdict_repr(defdictobject *dd)
return result;
}

static PyObject*
defdict_or(PyObject* left, PyObject* right)
{
int left_is_self = PyObject_IsInstance(left, (PyObject*)&defdict_type);
if (left_is_self < 0) {
return NULL;
}
PyObject *self, *other;
if (left_is_self) {
self = left;
other = right;
}
else {
self = right;
other = left;
}
if (!PyDict_Check(other)) {
Py_RETURN_NOTIMPLEMENTED;
}
// Like copy(), this calls the object's class.
// Override __or__/__ror__ for subclasses with different constructors.
PyObject *new = new_defdict((defdictobject*)self, left);
if (!new) {
return NULL;
}
if (PyDict_Update(new, right)) {
Py_DECREF(new);
return NULL;
}
return new;
}

static PyNumberMethods defdict_as_number = {
.nb_or = defdict_or,
};

static int
defdict_traverse(PyObject *self, visitproc visit, void *arg)
{
Expand Down Expand Up @@ -2198,7 +2237,7 @@ static PyTypeObject defdict_type = {
0, /* tp_setattr */
0, /* tp_as_async */
(reprfunc)defdict_repr, /* tp_repr */
0, /* tp_as_number */
&defdict_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
Expand Down