diff -r 59d9d970b2d0 -r 951243de359a Doc/library/collections.rst --- a/Doc/library/collections.rst Fri Jan 11 12:34:48 2013 +0200 +++ b/Doc/library/collections.rst Fri Jan 11 12:29:59 2013 +0000 @@ -76,11 +76,13 @@ be modified to change which mappings are searched. The list should always contain at least one mapping. - .. method:: new_child() + .. method:: new_child(amap=None) - Returns a new :class:`ChainMap` containing a new :class:`dict` followed by - all of the maps in the current instance. A call to ``d.new_child()`` is - equivalent to: ``ChainMap({}, *d.maps)``. This method is used for + Returns a new :class:`ChainMap` containing a new map followed by + all of the maps in the current instance. If ``amap`` is specified, + it becomes the new map at the front of the list of mappings; if not + specified, an empty dict is used, so that a call to ``d.new_child()`` + is equivalent to: ``ChainMap({}, *d.maps)``. This method is used for creating subcontexts that can be updated without altering values in any of the parent mappings. diff -r 59d9d970b2d0 -r 951243de359a Lib/collections/__init__.py --- a/Lib/collections/__init__.py Fri Jan 11 12:34:48 2013 +0200 +++ b/Lib/collections/__init__.py Fri Jan 11 12:29:59 2013 +0000 @@ -821,9 +821,14 @@ __copy__ = copy - def new_child(self): # like Django's Context.push() - 'New ChainMap with a new dict followed by all previous maps.' - return self.__class__({}, *self.maps) + def new_child(self, amap=None): # like Django's Context.push() + ''' + New ChainMap with a new map followed by all previous maps. If no + map is provided, an empty dict is used. + ''' + if amap is None: + amap = {} + return self.__class__(amap, *self.maps) @property def parents(self): # like Django's Context.pop() diff -r 59d9d970b2d0 -r 951243de359a Lib/test/test_collections.py --- a/Lib/test/test_collections.py Fri Jan 11 12:34:48 2013 +0200 +++ b/Lib/test/test_collections.py Fri Jan 11 12:29:59 2013 +0000 @@ -112,6 +112,38 @@ self.assertEqual(dict(d), dict(a=1, b=2, c=30)) self.assertEqual(dict(d.items()), dict(a=1, b=2, c=30)) + def test_new_child(self): + 'Tests for changes for issue #16613.' + c = ChainMap() + c['a'] = 1 + c['b'] = 2 + m = {'b':20, 'c': 30} + d = c.new_child(m) + self.assertEqual(d.maps, [{'b':20, 'c':30}, {'a':1, 'b':2}]) # check internal state + self.assertIs(m, d.maps[0]) + + # Use a different map than a dict + class lowerdict(dict): + def __getitem__(self, key): + if isinstance(key, str): + key = key.lower() + return dict.__getitem__(self, key) + def __contains__(self, key): + if isinstance(key, str): + key = key.lower() + return dict.__contains__(self, key) + + c = ChainMap() + c['a'] = 1 + c['b'] = 2 + m = lowerdict(b=20, c=30) + d = c.new_child(m) + self.assertIs(m, d.maps[0]) + for key in 'abc': # check contains + self.assertIn(key, d) + for k, v in dict(a=1, B=20, C=30, z=100).items(): # check get + self.assertEqual(d.get(k, 100), v) + ################################################################################ ### Named Tuples