bpo-36144: Implement defaultdict union (GH-18729)

For PEP 585 (this isn't in the PEP but is an obvious follow-up).
This commit is contained in:
Brandt Bucher 2020-03-06 09:24:08 -08:00 committed by GitHub
parent 9566842e89
commit 57c9d17256
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 6 deletions

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -183,5 +183,43 @@ class TestDefaultDict(unittest.TestCase):
o = pickle.loads(s)
self.assertEqual(d, o)
def test_union(self):
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()

View File

@ -0,0 +1 @@
:class:`collections.defaultdict` now implements ``|`` (:pep:`584`).

View File

@ -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 *
@ -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 *
@ -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)
{
@ -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 */