Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4285,6 +4285,11 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
LIFO order is now guaranteed. In prior versions, :meth:`popitem` would
return an arbitrary key/value pair.

.. describe:: reversed(d)

Return a reversed iterator over the keys of the dictionary. This is a
shortcut for ``reversed(d.keys())``.
Comment thread
remilapeyre marked this conversation as resolved.

Comment thread
methane marked this conversation as resolved.
.. method:: setdefault(key[, default])

If *key* is in the dictionary, return its value. If not, insert *key*
Expand Down Expand Up @@ -4332,6 +4337,22 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
Dictionary order is guaranteed to be insertion order. This behavior was
implementation detail of CPython from 3.6.

Dictionaries and dictionary views are reversible. ::

>>> d = {"one": 1, "two": 2, "three": 3, "four": 4}
>>> d
{'one': 1, 'two': 2, 'three': 3, 'four': 4}
>>> list(reversed(d))
['four', 'three', 'two', 'one']
>>> list(reversed(d.values()))
[4, 3, 2, 1]
>>> list(reversed(d.items()))
[('four', 4), ('three', 3), ('two', 2), ('one', 1)]

.. versionchanged:: 3.8
Dictionaries are now reversible.


.. seealso::
:class:`types.MappingProxyType` can be used to create a read-only view
of a :class:`dict`.
Expand Down Expand Up @@ -4375,6 +4396,14 @@ support membership tests:
Return ``True`` if *x* is in the underlying dictionary's keys, values or
items (in the latter case, *x* should be a ``(key, value)`` tuple).

.. describe:: reversed(dictview)

Return an reversed iterator over the keys, values or items of the dictionnary.
The view will be iterated in reverse order of the insertion.

.. versionchanged:: 3.8
Dictionary views are now reversible.


Keys views are set-like since their entries are unique and hashable. If all
values are hashable, so that ``(key, value)`` pairs are unique and hashable,
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ Other Language Changes
* Added support of ``\N{name}`` escapes in :mod:`regular expressions <re>`.
(Contributed by Jonathan Eunice and Serhiy Storchaka in :issue:`30688`.)

* Dict and dictviews are now iterable in reversed insertion order using
:func:`reversed`. (Contributed by Rémi Lapeyre in :issue:`33462`.)

* The syntax allowed for keyword names in function calls was further
restricted. In particular, ``f((keyword)=arg)`` is no longer allowed. It was
never intended to permit more than a bare name on the left-hand side of a
Expand Down
3 changes: 3 additions & 0 deletions Include/dictobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ PyAPI_DATA(PyTypeObject) PyDict_Type;
PyAPI_DATA(PyTypeObject) PyDictIterKey_Type;
PyAPI_DATA(PyTypeObject) PyDictIterValue_Type;
PyAPI_DATA(PyTypeObject) PyDictIterItem_Type;
PyAPI_DATA(PyTypeObject) PyDictRevIterKey_Type;
PyAPI_DATA(PyTypeObject) PyDictRevIterItem_Type;
PyAPI_DATA(PyTypeObject) PyDictRevIterValue_Type;
PyAPI_DATA(PyTypeObject) PyDictKeys_Type;
PyAPI_DATA(PyTypeObject) PyDictItems_Type;
PyAPI_DATA(PyTypeObject) PyDictValues_Type;
Expand Down
25 changes: 12 additions & 13 deletions Lib/test/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,22 +796,21 @@ class ItBlocked(It):

def test_Reversible(self):
# Check some non-reversibles
non_samples = [None, 42, 3.14, 1j, dict(), set(), frozenset()]
non_samples = [None, 42, 3.14, 1j, set(), frozenset()]
for x in non_samples:
self.assertNotIsInstance(x, Reversible)
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
# Check some non-reversible iterables
non_reversibles = [dict().keys(), dict().items(), dict().values(),
Counter(), Counter().keys(), Counter().items(),
Counter().values(), _test_gen(),
(x for x in []), iter([]), reversed([])]
non_reversibles = [_test_gen(), (x for x in []), iter([]), reversed([])]
for x in non_reversibles:
self.assertNotIsInstance(x, Reversible)
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
# Check some reversible iterables
samples = [bytes(), str(), tuple(), list(), OrderedDict(),
OrderedDict().keys(), OrderedDict().items(),
OrderedDict().values()]
OrderedDict().values(), Counter(), Counter().keys(),
Counter().items(), Counter().values(), dict(),
dict().keys(), dict().items(), dict().values()]
for x in samples:
self.assertIsInstance(x, Reversible)
self.assertTrue(issubclass(type(x), Reversible), repr(type(x)))
Expand Down Expand Up @@ -1612,7 +1611,7 @@ def test_MutableMapping_subclass(self):
self.assertIsInstance(z, set)
list(z)
mymap['blue'] = 7 # Shouldn't affect 'z'
self.assertEqual(sorted(z), [('orange', 3), ('red', 5)])
self.assertEqual(z, {('orange', 3), ('red', 5)})

def test_Sequence(self):
for sample in [tuple, list, bytes, str]:
Expand Down Expand Up @@ -1767,10 +1766,10 @@ def test_basics(self):
self.assertTrue(issubclass(Counter, Mapping))
self.assertEqual(len(c), 3)
self.assertEqual(sum(c.values()), 6)
self.assertEqual(sorted(c.values()), [1, 2, 3])
self.assertEqual(sorted(c.keys()), ['a', 'b', 'c'])
self.assertEqual(sorted(c), ['a', 'b', 'c'])
self.assertEqual(sorted(c.items()),
self.assertEqual(list(c.values()), [3, 2, 1])
self.assertEqual(list(c.keys()), ['a', 'b', 'c'])
self.assertEqual(list(c), ['a', 'b', 'c'])
self.assertEqual(list(c.items()),
[('a', 3), ('b', 2), ('c', 1)])
self.assertEqual(c['b'], 2)
self.assertEqual(c['z'], 0)
Expand All @@ -1784,7 +1783,7 @@ def test_basics(self):
for i in range(5):
self.assertEqual(c.most_common(i),
[('a', 3), ('b', 2), ('c', 1)][:i])
self.assertEqual(''.join(sorted(c.elements())), 'aaabbc')
self.assertEqual(''.join(c.elements()), 'aaabbc')
c['a'] += 1 # increment an existing value
c['b'] -= 2 # sub existing value to zero
del c['c'] # remove an entry
Expand All @@ -1793,7 +1792,7 @@ def test_basics(self):
c['e'] = -5 # directly assign a missing value
c['f'] += 4 # add to a missing value
self.assertEqual(c, dict(a=4, b=0, d=-2, e=-5, f=4))
self.assertEqual(''.join(sorted(c.elements())), 'aaaaffff')
self.assertEqual(''.join(c.elements()), 'aaaaffff')
self.assertEqual(c.pop('f'), 4)
self.assertNotIn('f', c)
for i in range(3):
Expand Down
69 changes: 66 additions & 3 deletions Lib/test/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -1021,7 +1021,7 @@ def test_iterator_pickling(self):
it = iter(data)
d = pickle.dumps(it, proto)
it = pickle.loads(d)
self.assertEqual(sorted(it), sorted(data))
self.assertEqual(list(it), list(data))

it = pickle.loads(d)
try:
Expand All @@ -1031,7 +1031,7 @@ def test_iterator_pickling(self):
d = pickle.dumps(it, proto)
it = pickle.loads(d)
del data[drop]
self.assertEqual(sorted(it), sorted(data))
self.assertEqual(list(it), list(data))

def test_itemiterator_pickling(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
Expand Down Expand Up @@ -1062,7 +1062,7 @@ def test_valuesiterator_pickling(self):
it = iter(data.values())
d = pickle.dumps(it, proto)
it = pickle.loads(d)
self.assertEqual(sorted(list(it)), sorted(list(data.values())))
self.assertEqual(list(it), list(data.values()))

it = pickle.loads(d)
drop = next(it)
Expand All @@ -1071,6 +1071,62 @@ def test_valuesiterator_pickling(self):
values = list(it) + [drop]
self.assertEqual(sorted(values), sorted(list(data.values())))

def test_reverseiterator_pickling(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
data = {1:"a", 2:"b", 3:"c"}
it = reversed(data)
d = pickle.dumps(it, proto)
it = pickle.loads(d)
self.assertEqual(list(it), list(reversed(data)))

it = pickle.loads(d)
try:
drop = next(it)
except StopIteration:
continue
d = pickle.dumps(it, proto)
it = pickle.loads(d)
del data[drop]
self.assertEqual(list(it), list(reversed(data)))

def test_reverseitemiterator_pickling(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
data = {1:"a", 2:"b", 3:"c"}
# dictviews aren't picklable, only their iterators
itorg = reversed(data.items())
d = pickle.dumps(itorg, proto)
it = pickle.loads(d)
# note that the type of the unpickled iterator
# is not necessarily the same as the original. It is
# merely an object supporting the iterator protocol, yielding
# the same objects as the original one.
# self.assertEqual(type(itorg), type(it))
self.assertIsInstance(it, collections.abc.Iterator)
self.assertEqual(dict(it), data)

it = pickle.loads(d)
drop = next(it)
d = pickle.dumps(it, proto)
it = pickle.loads(d)
del data[drop[0]]
self.assertEqual(dict(it), data)

def test_reversevaluesiterator_pickling(self):
for proto in range(pickle.HIGHEST_PROTOCOL):
data = {1:"a", 2:"b", 3:"c"}
# data.values() isn't picklable, only its iterator
it = reversed(data.values())
d = pickle.dumps(it, proto)
it = pickle.loads(d)
self.assertEqual(list(it), list(reversed(data.values())))

it = pickle.loads(d)
drop = next(it)
d = pickle.dumps(it, proto)
it = pickle.loads(d)
values = list(it) + [drop]
self.assertEqual(sorted(values), sorted(data.values()))

def test_instance_dict_getattr_str_subclass(self):
class Foo:
def __init__(self, msg):
Expand Down Expand Up @@ -1222,6 +1278,13 @@ def iter_and_mutate():

self.assertRaises(RuntimeError, iter_and_mutate)

def test_reversed(self):
d = {"a": 1, "b": 2, "foo": 0, "c": 3, "d": 4}
del d["foo"]
r = reversed(d)
self.assertEqual(list(r), list('dcba'))
self.assertRaises(StopIteration, next, r)

def test_dict_copy_order(self):
# bpo-34320
od = collections.OrderedDict([('a', 1), ('b', 2)])
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_enumerate.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ def __getitem__(self, i):
raise StopIteration
def __len__(self):
return 5
for data in 'abc', range(5), tuple(enumerate('abc')), A(), range(1,17,5):
for data in ('abc', range(5), tuple(enumerate('abc')), A(),
range(1,17,5), dict.fromkeys('abcde')):
self.assertEqual(list(data)[::-1], list(reversed(data)))
self.assertRaises(TypeError, reversed, {})
# don't allow keyword arguments
self.assertRaises(TypeError, reversed, [], a=1)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make dict and dict views reversible. Patch by Rémi Lapeyre.
20 changes: 19 additions & 1 deletion Objects/clinic/dictobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading