From ba855e834867af232f1f1606aebd61012f4967bd Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Thu, 27 Feb 2020 21:26:11 -0800 Subject: [PATCH 01/15] Update ChainMap to include | and |= Created __ior__, __or__ and __ror__ methods in ChainMap class. --- Lib/collections/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 1aa7d10ad22e4a..4d55c95e3dbd8a 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -961,6 +961,24 @@ def clear(self): 'Clear maps[0], leaving maps[1:] intact.' self.maps[0].clear() + def __ior__(self, other): + self.maps[0].update(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.MutableMapping): + m = other.copy() + for child in self.maps[::-1]: + m.update(child) + return self.__class__(m) + return NotImplemented ################################################################################ ### UserDict From 4d83fee0e028ffb048a0135c321657ad22033de0 Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Thu, 27 Feb 2020 21:28:01 -0800 Subject: [PATCH 02/15] Update ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index fe24a5636ccc28..f98022ba03cd0e 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 From 906eff0e682590974d11ddee775f740d37ce169d Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Thu, 27 Feb 2020 21:37:48 -0800 Subject: [PATCH 03/15] Update docs --- Doc/library/collections.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 65cdf34aa4e4fe..2e5d7a2780f9aa 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 From d0c6d40f85739a718be56a1585d40190a7881e38 Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Sat, 29 Feb 2020 13:05:33 -0800 Subject: [PATCH 04/15] Update test_collections.py to include test_issue584(). Added testing for | and |= operators for ChainMap objects. --- Lib/test/test_collections.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 92520b09bb8f26..5cf4c46c93b2da 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -232,6 +232,30 @@ def __contains__(self, key): 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_issue584(self): + 'Tests for changes in issue584 dealing with | and |= operators' + a = ChainMap(dict(a=1, b=2), dict(c=3, d=4)) + b = ChainMap(dict(a=10, e=5), dict(b=20, d=4)) + c = dict(a = 10, c = 30) + + ## Testing | operator between chainmaps + d = a | b + self.assertEqual(d, ChainMap(a.maps[0] | dict(b), *a.maps[1:])) + + ## Testing |= operator between chainmaps + a |= b + self.assertEqual(d, a) + + ## Testing | operator between chainmap and mapping, and vice versa + e = b | c + self.assertEqual(e, ChainMap(e.maps[0] | c, *b.maps[1:])) + + f = c | b + self.assertEqual(f, ChainMap(c | dict(b))) + + ## Testing |= operator between chainmap and mapping + b |= c + self.assertEqual(e,b) ################################################################################ ### Named Tuples From ca6d4782e23ebf164ad16c8496bb341b64b23576 Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Sun, 1 Mar 2020 17:53:48 -0800 Subject: [PATCH 05/15] Update test_union_operators Renamed test_union operators, fixed errors and style problems raised by brandtbucher. --- Lib/test/test_collections.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 5cf4c46c93b2da..9f7566d94e7bd3 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -232,11 +232,10 @@ def __contains__(self, key): 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_issue584(self): - 'Tests for changes in issue584 dealing with | and |= operators' + def test_union_operators(self): a = ChainMap(dict(a=1, b=2), dict(c=3, d=4)) b = ChainMap(dict(a=10, e=5), dict(b=20, d=4)) - c = dict(a = 10, c = 30) + c = dict(a=10, c=30) ## Testing | operator between chainmaps d = a | b @@ -248,14 +247,14 @@ def test_issue584(self): ## Testing | operator between chainmap and mapping, and vice versa e = b | c - self.assertEqual(e, ChainMap(e.maps[0] | c, *b.maps[1:])) + self.assertEqual(e, ChainMap(b.maps[0] | c, *b.maps[1:])) f = c | b self.assertEqual(f, ChainMap(c | dict(b))) ## Testing |= operator between chainmap and mapping b |= c - self.assertEqual(e,b) + self.assertEqual(e, b) ################################################################################ ### Named Tuples From dd2ab1aa50d8ebeac60d3a480ae27b81c87edf43 Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Sun, 1 Mar 2020 18:33:43 -0800 Subject: [PATCH 06/15] Update test_union_operators in TestChainMap Added testing for union operator between ChainMap and iterable of key-value pairs. --- Lib/test/test_collections.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 9f7566d94e7bd3..b21354083fc765 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -236,25 +236,26 @@ def test_union_operators(self): a = ChainMap(dict(a=1, b=2), dict(c=3, d=4)) b = ChainMap(dict(a=10, e=5), dict(b=20, d=4)) c = dict(a=10, c=30) + d = [('a', 1), ('c',3)] - ## Testing | operator between chainmaps - d = a | b - self.assertEqual(d, ChainMap(a.maps[0] | dict(b), *a.maps[1:])) - - ## Testing |= operator between chainmaps + e = a | b # testing between chainmaps + self.assertEqual(e, ChainMap(a.maps[0] | dict(b), *a.maps[1:])) a |= b - self.assertEqual(d, a) - - ## Testing | operator between chainmap and mapping, and vice versa - e = b | c - self.assertEqual(e, ChainMap(b.maps[0] | c, *b.maps[1:])) - - f = c | b - self.assertEqual(f, ChainMap(c | dict(b))) + self.assertEqual(e, a) + + f = b | c # testing between chainmap and mapping + self.assertEqual(f, ChainMap(b.maps[0] | c, *b.maps[1:])) + g = c | b + self.assertEqual(g, ChainMap(c | dict(b))) + b |= c + self.assertEqual(f, b) + + # testing behavior between chainmap and iterable key-value pairs + with self.assertRaises(TypeError): + a | d + a |= d + self.assertEqual(a, ChainMap(a.maps[0] | dict(d)), *a.maps[1:]) - ## Testing |= operator between chainmap and mapping - b |= c - self.assertEqual(e, b) ################################################################################ ### Named Tuples From fe9b91d876ac98a1ae9bb6683417663a08924d27 Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Sun, 1 Mar 2020 23:13:28 -0800 Subject: [PATCH 07/15] Update test_union operators in test_collections.py Gave more descriptive variable names and eliminated unnecessary tmp variable. --- Lib/test/test_collections.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index b21354083fc765..6702a75c244454 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -233,28 +233,27 @@ def __contains__(self, key): self.assertEqual(d.get(k, 100), v) def test_union_operators(self): - a = ChainMap(dict(a=1, b=2), dict(c=3, d=4)) - b = ChainMap(dict(a=10, e=5), dict(b=20, d=4)) - c = dict(a=10, c=30) - d = [('a', 1), ('c',3)] - - e = a | b # testing between chainmaps - self.assertEqual(e, ChainMap(a.maps[0] | dict(b), *a.maps[1:])) - a |= b - self.assertEqual(e, a) + cm1 = ChainMap(dict(a=1, b=2), dict(c=3, d=4)) + cm2 = ChainMap(dict(a=10, e=5), dict(b=20, d=4)) + d = dict(a=10, c=30) + pairs = [('a', 1), ('c',3)] + + tmp = cm1 | cm2 # testing between chainmaps + self.assertEqual(tmp, ChainMap(cm1.maps[0] | dict(cm2), *cm1.maps[1:])) + cm1 |= cm2 + self.assertEqual(tmp, cm1) - f = b | c # testing between chainmap and mapping - self.assertEqual(f, ChainMap(b.maps[0] | c, *b.maps[1:])) - g = c | b - self.assertEqual(g, ChainMap(c | dict(b))) - b |= c - self.assertEqual(f, b) + tmp = cm2 | d # testing between chainmap and mapping + self.assertEqual(tmp, ChainMap(cm2.maps[0] | d, *cm2.maps[1:])) + self.assertEqual(d | cm2, ChainMap(d | dict(cm2))) + cm2 |= d + self.assertEqual(tmp, cm2) # testing behavior between chainmap and iterable key-value pairs with self.assertRaises(TypeError): - a | d - a |= d - self.assertEqual(a, ChainMap(a.maps[0] | dict(d)), *a.maps[1:]) + cm1 | pairs + cm1 |= pairs + self.assertEqual(cm1, ChainMap(cm1.maps[0] | dict(pairs)), *cm1.maps[1:]) ################################################################################ From 4c7f937a0a433e6ecad8fc5cb9313f5e1ade6d9c Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Mon, 2 Mar 2020 17:46:35 -0800 Subject: [PATCH 08/15] Update test_union_operators in test_collections.py Added cm3 --- Lib/test/test_collections.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 6702a75c244454..3e63dab9df534e 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -235,8 +235,9 @@ def __contains__(self, key): 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 = ChainMap(dict(a=1, b=2), dict(c=3, d=4)) d = dict(a=10, c=30) - pairs = [('a', 1), ('c',3)] + pairs = [('c', 3), ('p',0)] tmp = cm1 | cm2 # testing between chainmaps self.assertEqual(tmp, ChainMap(cm1.maps[0] | dict(cm2), *cm1.maps[1:])) @@ -251,9 +252,9 @@ def test_union_operators(self): # testing behavior between chainmap and iterable key-value pairs with self.assertRaises(TypeError): - cm1 | pairs - cm1 |= pairs - self.assertEqual(cm1, ChainMap(cm1.maps[0] | dict(pairs)), *cm1.maps[1:]) + cm3 | pairs + cm3 |= pairs + self.assertEqual(cm3, ChainMap(cm3.maps[0] | dict(pairs), *cm3.maps[1:])) ################################################################################ From 28b03fc4833552e288c29e2cdc794b15af8314a6 Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Sat, 7 Mar 2020 11:23:28 -0800 Subject: [PATCH 09/15] Check .maps rather than Chainmap equality. --- Lib/test/test_collections.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 3e63dab9df534e..fc4b70b6b7eaa3 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -235,18 +235,18 @@ def __contains__(self, key): 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 = ChainMap(dict(a=1, b=2), dict(c=3, 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, ChainMap(cm1.maps[0] | dict(cm2), *cm1.maps[1:])) + 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, ChainMap(cm2.maps[0] | d, *cm2.maps[1:])) - self.assertEqual(d | cm2, ChainMap(d | dict(cm2))) + 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) @@ -254,8 +254,7 @@ def test_union_operators(self): with self.assertRaises(TypeError): cm3 | pairs cm3 |= pairs - self.assertEqual(cm3, ChainMap(cm3.maps[0] | dict(pairs), *cm3.maps[1:])) - + self.assertEqual(cm3.maps, [cm3.maps[0] | dict(pairs), *cm3.maps[1:]]) ################################################################################ ### Named Tuples From dfac64a0934e3774a2c7b0e13f4bf5921ba9477a Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Sat, 7 Mar 2020 11:26:21 -0800 Subject: [PATCH 10/15] Add news entry --- .../NEWS.d/next/Library/2020-03-07-11-26-08.bpo-36144.FG9jqy.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2020-03-07-11-26-08.bpo-36144.FG9jqy.rst 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 00000000000000..ea0cbd99423b39 --- /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 to :class:`collections.ChainMap`. From e5b2592536e64ece1d5b63b8f1be4d3306bf6c4a Mon Sep 17 00:00:00 2001 From: Curtis Bucher Date: Sat, 7 Mar 2020 11:48:21 -0800 Subject: [PATCH 11/15] Update Lib/test/test_collections.py Co-Authored-By: Brandt Bucher --- Lib/test/test_collections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index fc4b70b6b7eaa3..4100250ce21a01 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -247,7 +247,7 @@ def test_union_operators(self): 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 + cm2 |= d self.assertEqual(tmp, cm2) # testing behavior between chainmap and iterable key-value pairs From df57d592a41f5a72c99ea3e56004ef65c63f97b0 Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Sat, 7 Mar 2020 11:56:26 -0800 Subject: [PATCH 12/15] Removed whitespace --- Lib/test/test_collections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 4100250ce21a01..af8d1a5c55540d 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -243,7 +243,7 @@ def test_union_operators(self): 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)]) From 97a156dc711056179bf841c38165ed38a98943cb Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Tue, 10 Mar 2020 19:36:27 -0700 Subject: [PATCH 13/15] Added Guido's changes --- Lib/collections/__init__.py | 9 ++++---- Lib/test/test_collections.py | 22 +++++++++++++++++++ .../2020-03-07-11-26-08.bpo-36144.FG9jqy.rst | 2 +- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 4d55c95e3dbd8a..16fd3118b0c912 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -962,7 +962,7 @@ def clear(self): self.maps[0].clear() def __ior__(self, other): - self.maps[0].update(other) + self.maps[0] |= other return self def __or__(self, other): @@ -973,13 +973,14 @@ def __or__(self, other): return NotImplemented def __ror__(self, other): - if isinstance(other, _collections_abc.MutableMapping): - m = other.copy() - for child in self.maps[::-1]: + 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 af8d1a5c55540d..0c2879b580ba2e 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -256,6 +256,28 @@ def test_union_operators(self): 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/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 index ea0cbd99423b39..4a4545a1a2fb5c 100644 --- 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 @@ -1 +1 @@ -Added :pep:`584` operators to :class:`collections.ChainMap`. +Added :pep:`584` operators (| and |=) to :class:`collections.ChainMap`. From 60904c2331ee3d9085d130296cb9200284d6a3c1 Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Tue, 10 Mar 2020 19:44:46 -0700 Subject: [PATCH 14/15] Fixed Docs --- .../next/Library/2020-03-07-11-26-08.bpo-36144.FG9jqy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4a4545a1a2fb5c..9deb489d883525 100644 --- 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 @@ -1 +1 @@ -Added :pep:`584` operators (| and |=) to :class:`collections.ChainMap`. +Added :pep:`584` operators (``|`` and ``|=``) to :class:`collections.ChainMap`. From 5d63457f95b2c8e6098a0aee2329bf8ad566741f Mon Sep 17 00:00:00 2001 From: curtisbucher Date: Tue, 10 Mar 2020 19:52:37 -0700 Subject: [PATCH 15/15] Removed whitespace --- Lib/test/test_collections.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 0c2879b580ba2e..47c500bcdd0025 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -259,11 +259,11 @@ def test_union_operators(self): # 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)