Skip to content

Commit f393b2c

Browse files
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 <brandtbucher@gmail.com> * Removed whitespace * Added Guido's changes * Fixed Docs * Removed whitespace Co-authored-by: Brandt Bucher <brandtbucher@gmail.com>
1 parent 8ec7370 commit f393b2c

File tree

5 files changed

+69
-0
lines changed

5 files changed

+69
-0
lines changed

Doc/library/collections.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ The class can be used to simulate nested scopes and is useful in templating.
116116
>>> list(combined)
117117
['music', 'art', 'opera']
118118

119+
.. versionchanged:: 3.9
120+
Added support for ``|`` and ``|=`` operators, specified in :pep:`584`.
121+
119122
.. seealso::
120123

121124
* The `MultiContext class

Lib/collections/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,25 @@ def clear(self):
979979
'Clear maps[0], leaving maps[1:] intact.'
980980
self.maps[0].clear()
981981

982+
def __ior__(self, other):
983+
self.maps[0] |= other
984+
return self
985+
986+
def __or__(self, other):
987+
if isinstance(other, _collections_abc.Mapping):
988+
m = self.maps[0].copy()
989+
m.update(other)
990+
return self.__class__(m, *self.maps[1:])
991+
return NotImplemented
992+
993+
def __ror__(self, other):
994+
if isinstance(other, _collections_abc.Mapping):
995+
m = dict(other)
996+
for child in reversed(self.maps):
997+
m.update(child)
998+
return self.__class__(m)
999+
return NotImplemented
1000+
9821001

9831002
################################################################################
9841003
### UserDict

Lib/test/test_collections.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,51 @@ def __contains__(self, key):
232232
for k, v in dict(a=1, B=20, C=30, z=100).items(): # check get
233233
self.assertEqual(d.get(k, 100), v)
234234

235+
def test_union_operators(self):
236+
cm1 = ChainMap(dict(a=1, b=2), dict(c=3, d=4))
237+
cm2 = ChainMap(dict(a=10, e=5), dict(b=20, d=4))
238+
cm3 = cm1.copy()
239+
d = dict(a=10, c=30)
240+
pairs = [('c', 3), ('p',0)]
241+
242+
tmp = cm1 | cm2 # testing between chainmaps
243+
self.assertEqual(tmp.maps, [cm1.maps[0] | dict(cm2), *cm1.maps[1:]])
244+
cm1 |= cm2
245+
self.assertEqual(tmp, cm1)
246+
247+
tmp = cm2 | d # testing between chainmap and mapping
248+
self.assertEqual(tmp.maps, [cm2.maps[0] | d, *cm2.maps[1:]])
249+
self.assertEqual((d | cm2).maps, [d | dict(cm2)])
250+
cm2 |= d
251+
self.assertEqual(tmp, cm2)
252+
253+
# testing behavior between chainmap and iterable key-value pairs
254+
with self.assertRaises(TypeError):
255+
cm3 | pairs
256+
cm3 |= pairs
257+
self.assertEqual(cm3.maps, [cm3.maps[0] | dict(pairs), *cm3.maps[1:]])
258+
259+
# testing proper return types for ChainMap and it's subclasses
260+
class Subclass(ChainMap):
261+
pass
262+
263+
class SubclassRor(ChainMap):
264+
def __ror__(self, other):
265+
return super().__ror__(other)
266+
267+
tmp = ChainMap() | ChainMap()
268+
self.assertIs(type(tmp), ChainMap)
269+
self.assertIs(type(tmp.maps[0]), dict)
270+
tmp = ChainMap() | Subclass()
271+
self.assertIs(type(tmp), ChainMap)
272+
self.assertIs(type(tmp.maps[0]), dict)
273+
tmp = Subclass() | ChainMap()
274+
self.assertIs(type(tmp), Subclass)
275+
self.assertIs(type(tmp.maps[0]), dict)
276+
tmp = ChainMap() | SubclassRor()
277+
self.assertIs(type(tmp), SubclassRor)
278+
self.assertIs(type(tmp.maps[0]), dict)
279+
235280

236281
################################################################################
237282
### Named Tuples

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ Floris Bruynooghe
233233
Matt Bryant
234234
Stan Bubrouski
235235
Brandt Bucher
236+
Curtis Bucher
236237
Colm Buckley
237238
Erik de Bueger
238239
Jan-Hein Bührman
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added :pep:`584` operators (``|`` and ``|=``) to :class:`collections.ChainMap`.

0 commit comments

Comments
 (0)