From f393b2c588559162dc2e77f8079a42e48558870a Mon Sep 17 00:00:00 2001 From: Curtis Bucher Date: Mon, 23 Mar 2020 12:02:05 -0700 Subject: bpo-36144: Add PEP 584 operators to collections.ChainMap (#18832) * Update ChainMap to include | and |= Created __ior__, __or__ and __ror__ methods in ChainMap class. * Update ACKS * Update docs * Update test_collections.py to include test_issue584(). Added testing for | and |= operators for ChainMap objects. * Update test_union_operators Renamed test_union operators, fixed errors and style problems raised by brandtbucher. * Update test_union_operators in TestChainMap Added testing for union operator between ChainMap and iterable of key-value pairs. * Update test_union operators in test_collections.py Gave more descriptive variable names and eliminated unnecessary tmp variable. * Update test_union_operators in test_collections.py Added cm3 * Check .maps rather than Chainmap equality. * Add news entry * Update Lib/test/test_collections.py Co-Authored-By: Brandt Bucher * Removed whitespace * Added Guido's changes * Fixed Docs * Removed whitespace Co-authored-by: Brandt Bucher --- Doc/library/collections.rst | 3 ++ Lib/collections/__init__.py | 19 +++++++++ Lib/test/test_collections.py | 45 ++++++++++++++++++++++ Misc/ACKS | 1 + .../2020-03-07-11-26-08.bpo-36144.FG9jqy.rst | 1 + 5 files changed, 69 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-03-07-11-26-08.bpo-36144.FG9jqy.rst diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 72d1052..c9533a3 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -116,6 +116,9 @@ The class can be used to simulate nested scopes and is useful in templating. >>> list(combined) ['music', 'art', 'opera'] + .. versionchanged:: 3.9 + Added support for ``|`` and ``|=`` operators, specified in :pep:`584`. + .. seealso:: * The `MultiContext class diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 18255da..e198406 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -979,6 +979,25 @@ class ChainMap(_collections_abc.MutableMapping): 'Clear maps[0], leaving maps[1:] intact.' self.maps[0].clear() + def __ior__(self, other): + self.maps[0] |= other + return self + + def __or__(self, other): + if isinstance(other, _collections_abc.Mapping): + m = self.maps[0].copy() + m.update(other) + return self.__class__(m, *self.maps[1:]) + return NotImplemented + + def __ror__(self, other): + if isinstance(other, _collections_abc.Mapping): + m = dict(other) + for child in reversed(self.maps): + m.update(child) + return self.__class__(m) + return NotImplemented + ################################################################################ ### UserDict diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 92520b0..47c500b 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -232,6 +232,51 @@ class TestChainMap(unittest.TestCase): for k, v in dict(a=1, B=20, C=30, z=100).items(): # check get self.assertEqual(d.get(k, 100), v) + def test_union_operators(self): + cm1 = ChainMap(dict(a=1, b=2), dict(c=3, d=4)) + cm2 = ChainMap(dict(a=10, e=5), dict(b=20, d=4)) + cm3 = cm1.copy() + d = dict(a=10, c=30) + pairs = [('c', 3), ('p',0)] + + tmp = cm1 | cm2 # testing between chainmaps + self.assertEqual(tmp.maps, [cm1.maps[0] | dict(cm2), *cm1.maps[1:]]) + cm1 |= cm2 + self.assertEqual(tmp, cm1) + + tmp = cm2 | d # testing between chainmap and mapping + self.assertEqual(tmp.maps, [cm2.maps[0] | d, *cm2.maps[1:]]) + self.assertEqual((d | cm2).maps, [d | dict(cm2)]) + cm2 |= d + self.assertEqual(tmp, cm2) + + # testing behavior between chainmap and iterable key-value pairs + with self.assertRaises(TypeError): + cm3 | pairs + cm3 |= pairs + self.assertEqual(cm3.maps, [cm3.maps[0] | dict(pairs), *cm3.maps[1:]]) + + # testing proper return types for ChainMap and it's subclasses + class Subclass(ChainMap): + pass + + class SubclassRor(ChainMap): + def __ror__(self, other): + return super().__ror__(other) + + tmp = ChainMap() | ChainMap() + self.assertIs(type(tmp), ChainMap) + self.assertIs(type(tmp.maps[0]), dict) + tmp = ChainMap() | Subclass() + self.assertIs(type(tmp), ChainMap) + self.assertIs(type(tmp.maps[0]), dict) + tmp = Subclass() | ChainMap() + self.assertIs(type(tmp), Subclass) + self.assertIs(type(tmp.maps[0]), dict) + tmp = ChainMap() | SubclassRor() + self.assertIs(type(tmp), SubclassRor) + self.assertIs(type(tmp.maps[0]), dict) + ################################################################################ ### Named Tuples diff --git a/Misc/ACKS b/Misc/ACKS index 37cf7af..129db95 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -233,6 +233,7 @@ Floris Bruynooghe Matt Bryant Stan Bubrouski Brandt Bucher +Curtis Bucher Colm Buckley Erik de Bueger Jan-Hein Bührman diff --git a/Misc/NEWS.d/next/Library/2020-03-07-11-26-08.bpo-36144.FG9jqy.rst b/Misc/NEWS.d/next/Library/2020-03-07-11-26-08.bpo-36144.FG9jqy.rst new file mode 100644 index 0000000..9deb489 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-03-07-11-26-08.bpo-36144.FG9jqy.rst @@ -0,0 +1 @@ +Added :pep:`584` operators (``|`` and ``|=``) to :class:`collections.ChainMap`. -- cgit v0.12