diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 454817b7aa4..1cc4097b6f8 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -793,6 +793,23 @@ the items are returned in the order their keys were first added. (key, value) pair. The pairs are returned in LIFO order if *last* is true or FIFO order if false. + .. method:: move_to_end(key, last=True) + + Move an existing *key* to either end of an ordered dictionary. The item + is moved to the right end if *last* is true (the default) or to the + beginning if *last* is false. Raises :exc:`KeyError` if the *key* does + not exist:: + + >>> d = OrderedDict.fromkeys('abcde') + >>> d.move_to_end('b') + >>> ''.join(d.keys) + 'acdeb' + >>> d.move_to_end('b', 0) + >>> ''.join(d.keys) + 'bacde' + + .. versionadded:: 3.2 + In addition to the usual mapping methods, ordered dictionaries also support reverse iteration using :func:`reversed`. diff --git a/Lib/collections.py b/Lib/collections.py index c3c51d120fa..00886ef06eb 100644 --- a/Lib/collections.py +++ b/Lib/collections.py @@ -173,18 +173,29 @@ class OrderedDict(dict, MutableMapping): def __del__(self): self.clear() # eliminate cyclical references - def _renew(self, key, PREV=0, NEXT=1): - 'Fast version of self[key]=self.pop(key). Private method for internal use.' + def move_to_end(self, key, last=True, PREV=0, NEXT=1): + '''Move an existing element to the end (or beginning if last==False). + + Raises KeyError if the element does not exist. + When last=True, acts like a fast version of self[key]=self.pop(key). + + ''' link = self.__map[key] link_prev = link[PREV] link_next = link[NEXT] link_prev[NEXT] = link_next link_next[PREV] = link_prev root = self.__root - last = root[PREV] - link[PREV] = last - link[NEXT] = root - last[NEXT] = root[PREV] = link + if last: + last = root[PREV] + link[PREV] = last + link[NEXT] = root + last[NEXT] = root[PREV] = link + else: + first = root[NEXT] + link[PREV] = root + link[NEXT] = first + root[NEXT] = first[PREV] = link ################################################################################ diff --git a/Lib/functools.py b/Lib/functools.py index b2df390529e..a723f66a3c5 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -127,7 +127,7 @@ def lru_cache(maxsize=100): len=len, KeyError=KeyError): cache = OrderedDict() # ordered least recent to most recent cache_popitem = cache.popitem - cache_renew = cache._renew + cache_renew = cache.move_to_end kwd_mark = object() # separate positional and keyword args lock = Lock() diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 75d660d6287..7beb061b2f4 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -973,7 +973,19 @@ class TestOrderedDict(unittest.TestCase): od['a'] = 1 self.assertEqual(list(od.items()), [('b', 2), ('a', 1)]) - + def test_move_to_end(self): + od = OrderedDict.fromkeys('abcde') + self.assertEqual(list(od), list('abcde')) + od.move_to_end('c') + self.assertEqual(list(od), list('abdec')) + od.move_to_end('c', 0) + self.assertEqual(list(od), list('cabde')) + od.move_to_end('c', 0) + self.assertEqual(list(od), list('cabde')) + od.move_to_end('e') + self.assertEqual(list(od), list('cabde')) + with self.assertRaises(KeyError): + od.move_to_end('x') class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): type2test = OrderedDict diff --git a/Misc/NEWS b/Misc/NEWS index d2d35d05da7..262983b3137 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Core and Builtins Library ------- +- collections.OrderedDict now supports a new method for repositioning + keys to either end. + - Issue #9754: Similarly to assertRaises and assertRaisesRegexp, unittest test cases now also have assertWarns and assertWarnsRegexp methods to check that a given warning type was triggered by the code under test.