Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6531bf6

Browse files
Rémi Lapeyremethane
authored andcommitted
bpo-33462: Add __reversed__ to dict and dict views (GH-6827)
1 parent 16c8a53 commit 6531bf6

10 files changed

Lines changed: 346 additions & 24 deletions

File tree

Doc/library/stdtypes.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4285,6 +4285,11 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098:
42854285
LIFO order is now guaranteed. In prior versions, :meth:`popitem` would
42864286
return an arbitrary key/value pair.
42874287

4288+
.. describe:: reversed(d)
4289+
4290+
Return a reversed iterator over the keys of the dictionary. This is a
4291+
shortcut for ``reversed(d.keys())``.
4292+
42884293
.. method:: setdefault(key[, default])
42894294

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

4340+
Dictionaries and dictionary views are reversible. ::
4341+
4342+
>>> d = {"one": 1, "two": 2, "three": 3, "four": 4}
4343+
>>> d
4344+
{'one': 1, 'two': 2, 'three': 3, 'four': 4}
4345+
>>> list(reversed(d))
4346+
['four', 'three', 'two', 'one']
4347+
>>> list(reversed(d.values()))
4348+
[4, 3, 2, 1]
4349+
>>> list(reversed(d.items()))
4350+
[('four', 4), ('three', 3), ('two', 2), ('one', 1)]
4351+
4352+
.. versionchanged:: 3.8
4353+
Dictionaries are now reversible.
4354+
4355+
43354356
.. seealso::
43364357
:class:`types.MappingProxyType` can be used to create a read-only view
43374358
of a :class:`dict`.
@@ -4375,6 +4396,14 @@ support membership tests:
43754396
Return ``True`` if *x* is in the underlying dictionary's keys, values or
43764397
items (in the latter case, *x* should be a ``(key, value)`` tuple).
43774398

4399+
.. describe:: reversed(dictview)
4400+
4401+
Return an reversed iterator over the keys, values or items of the dictionnary.
4402+
The view will be iterated in reverse order of the insertion.
4403+
4404+
.. versionchanged:: 3.8
4405+
Dictionary views are now reversible.
4406+
43784407

43794408
Keys views are set-like since their entries are unique and hashable. If all
43804409
values are hashable, so that ``(key, value)`` pairs are unique and hashable,

Doc/whatsnew/3.8.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ Other Language Changes
9898
* Added support of ``\N{name}`` escapes in :mod:`regular expressions <re>`.
9999
(Contributed by Jonathan Eunice and Serhiy Storchaka in :issue:`30688`.)
100100

101+
* Dict and dictviews are now iterable in reversed insertion order using
102+
:func:`reversed`. (Contributed by Rémi Lapeyre in :issue:`33462`.)
103+
101104
* The syntax allowed for keyword names in function calls was further
102105
restricted. In particular, ``f((keyword)=arg)`` is no longer allowed. It was
103106
never intended to permit more than a bare name on the left-hand side of a

Include/dictobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ PyAPI_DATA(PyTypeObject) PyDict_Type;
5151
PyAPI_DATA(PyTypeObject) PyDictIterKey_Type;
5252
PyAPI_DATA(PyTypeObject) PyDictIterValue_Type;
5353
PyAPI_DATA(PyTypeObject) PyDictIterItem_Type;
54+
PyAPI_DATA(PyTypeObject) PyDictRevIterKey_Type;
55+
PyAPI_DATA(PyTypeObject) PyDictRevIterItem_Type;
56+
PyAPI_DATA(PyTypeObject) PyDictRevIterValue_Type;
5457
PyAPI_DATA(PyTypeObject) PyDictKeys_Type;
5558
PyAPI_DATA(PyTypeObject) PyDictItems_Type;
5659
PyAPI_DATA(PyTypeObject) PyDictValues_Type;

Lib/test/test_collections.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -796,22 +796,21 @@ class ItBlocked(It):
796796

797797
def test_Reversible(self):
798798
# Check some non-reversibles
799-
non_samples = [None, 42, 3.14, 1j, dict(), set(), frozenset()]
799+
non_samples = [None, 42, 3.14, 1j, set(), frozenset()]
800800
for x in non_samples:
801801
self.assertNotIsInstance(x, Reversible)
802802
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
803803
# Check some non-reversible iterables
804-
non_reversibles = [dict().keys(), dict().items(), dict().values(),
805-
Counter(), Counter().keys(), Counter().items(),
806-
Counter().values(), _test_gen(),
807-
(x for x in []), iter([]), reversed([])]
804+
non_reversibles = [_test_gen(), (x for x in []), iter([]), reversed([])]
808805
for x in non_reversibles:
809806
self.assertNotIsInstance(x, Reversible)
810807
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
811808
# Check some reversible iterables
812809
samples = [bytes(), str(), tuple(), list(), OrderedDict(),
813810
OrderedDict().keys(), OrderedDict().items(),
814-
OrderedDict().values()]
811+
OrderedDict().values(), Counter(), Counter().keys(),
812+
Counter().items(), Counter().values(), dict(),
813+
dict().keys(), dict().items(), dict().values()]
815814
for x in samples:
816815
self.assertIsInstance(x, Reversible)
817816
self.assertTrue(issubclass(type(x), Reversible), repr(type(x)))
@@ -1612,7 +1611,7 @@ def test_MutableMapping_subclass(self):
16121611
self.assertIsInstance(z, set)
16131612
list(z)
16141613
mymap['blue'] = 7 # Shouldn't affect 'z'
1615-
self.assertEqual(sorted(z), [('orange', 3), ('red', 5)])
1614+
self.assertEqual(z, {('orange', 3), ('red', 5)})
16161615

16171616
def test_Sequence(self):
16181617
for sample in [tuple, list, bytes, str]:
@@ -1767,10 +1766,10 @@ def test_basics(self):
17671766
self.assertTrue(issubclass(Counter, Mapping))
17681767
self.assertEqual(len(c), 3)
17691768
self.assertEqual(sum(c.values()), 6)
1770-
self.assertEqual(sorted(c.values()), [1, 2, 3])
1771-
self.assertEqual(sorted(c.keys()), ['a', 'b', 'c'])
1772-
self.assertEqual(sorted(c), ['a', 'b', 'c'])
1773-
self.assertEqual(sorted(c.items()),
1769+
self.assertEqual(list(c.values()), [3, 2, 1])
1770+
self.assertEqual(list(c.keys()), ['a', 'b', 'c'])
1771+
self.assertEqual(list(c), ['a', 'b', 'c'])
1772+
self.assertEqual(list(c.items()),
17741773
[('a', 3), ('b', 2), ('c', 1)])
17751774
self.assertEqual(c['b'], 2)
17761775
self.assertEqual(c['z'], 0)
@@ -1784,7 +1783,7 @@ def test_basics(self):
17841783
for i in range(5):
17851784
self.assertEqual(c.most_common(i),
17861785
[('a', 3), ('b', 2), ('c', 1)][:i])
1787-
self.assertEqual(''.join(sorted(c.elements())), 'aaabbc')
1786+
self.assertEqual(''.join(c.elements()), 'aaabbc')
17881787
c['a'] += 1 # increment an existing value
17891788
c['b'] -= 2 # sub existing value to zero
17901789
del c['c'] # remove an entry
@@ -1793,7 +1792,7 @@ def test_basics(self):
17931792
c['e'] = -5 # directly assign a missing value
17941793
c['f'] += 4 # add to a missing value
17951794
self.assertEqual(c, dict(a=4, b=0, d=-2, e=-5, f=4))
1796-
self.assertEqual(''.join(sorted(c.elements())), 'aaaaffff')
1795+
self.assertEqual(''.join(c.elements()), 'aaaaffff')
17971796
self.assertEqual(c.pop('f'), 4)
17981797
self.assertNotIn('f', c)
17991798
for i in range(3):

Lib/test/test_dict.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,7 +1021,7 @@ def test_iterator_pickling(self):
10211021
it = iter(data)
10221022
d = pickle.dumps(it, proto)
10231023
it = pickle.loads(d)
1024-
self.assertEqual(sorted(it), sorted(data))
1024+
self.assertEqual(list(it), list(data))
10251025

10261026
it = pickle.loads(d)
10271027
try:
@@ -1031,7 +1031,7 @@ def test_iterator_pickling(self):
10311031
d = pickle.dumps(it, proto)
10321032
it = pickle.loads(d)
10331033
del data[drop]
1034-
self.assertEqual(sorted(it), sorted(data))
1034+
self.assertEqual(list(it), list(data))
10351035

10361036
def test_itemiterator_pickling(self):
10371037
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
@@ -1062,7 +1062,7 @@ def test_valuesiterator_pickling(self):
10621062
it = iter(data.values())
10631063
d = pickle.dumps(it, proto)
10641064
it = pickle.loads(d)
1065-
self.assertEqual(sorted(list(it)), sorted(list(data.values())))
1065+
self.assertEqual(list(it), list(data.values()))
10661066

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

1074+
def test_reverseiterator_pickling(self):
1075+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1076+
data = {1:"a", 2:"b", 3:"c"}
1077+
it = reversed(data)
1078+
d = pickle.dumps(it, proto)
1079+
it = pickle.loads(d)
1080+
self.assertEqual(list(it), list(reversed(data)))
1081+
1082+
it = pickle.loads(d)
1083+
try:
1084+
drop = next(it)
1085+
except StopIteration:
1086+
continue
1087+
d = pickle.dumps(it, proto)
1088+
it = pickle.loads(d)
1089+
del data[drop]
1090+
self.assertEqual(list(it), list(reversed(data)))
1091+
1092+
def test_reverseitemiterator_pickling(self):
1093+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
1094+
data = {1:"a", 2:"b", 3:"c"}
1095+
# dictviews aren't picklable, only their iterators
1096+
itorg = reversed(data.items())
1097+
d = pickle.dumps(itorg, proto)
1098+
it = pickle.loads(d)
1099+
# note that the type of the unpickled iterator
1100+
# is not necessarily the same as the original. It is
1101+
# merely an object supporting the iterator protocol, yielding
1102+
# the same objects as the original one.
1103+
# self.assertEqual(type(itorg), type(it))
1104+
self.assertIsInstance(it, collections.abc.Iterator)
1105+
self.assertEqual(dict(it), data)
1106+
1107+
it = pickle.loads(d)
1108+
drop = next(it)
1109+
d = pickle.dumps(it, proto)
1110+
it = pickle.loads(d)
1111+
del data[drop[0]]
1112+
self.assertEqual(dict(it), data)
1113+
1114+
def test_reversevaluesiterator_pickling(self):
1115+
for proto in range(pickle.HIGHEST_PROTOCOL):
1116+
data = {1:"a", 2:"b", 3:"c"}
1117+
# data.values() isn't picklable, only its iterator
1118+
it = reversed(data.values())
1119+
d = pickle.dumps(it, proto)
1120+
it = pickle.loads(d)
1121+
self.assertEqual(list(it), list(reversed(data.values())))
1122+
1123+
it = pickle.loads(d)
1124+
drop = next(it)
1125+
d = pickle.dumps(it, proto)
1126+
it = pickle.loads(d)
1127+
values = list(it) + [drop]
1128+
self.assertEqual(sorted(values), sorted(data.values()))
1129+
10741130
def test_instance_dict_getattr_str_subclass(self):
10751131
class Foo:
10761132
def __init__(self, msg):
@@ -1222,6 +1278,13 @@ def iter_and_mutate():
12221278

12231279
self.assertRaises(RuntimeError, iter_and_mutate)
12241280

1281+
def test_reversed(self):
1282+
d = {"a": 1, "b": 2, "foo": 0, "c": 3, "d": 4}
1283+
del d["foo"]
1284+
r = reversed(d)
1285+
self.assertEqual(list(r), list('dcba'))
1286+
self.assertRaises(StopIteration, next, r)
1287+
12251288
def test_dict_copy_order(self):
12261289
# bpo-34320
12271290
od = collections.OrderedDict([('a', 1), ('b', 2)])

Lib/test/test_enumerate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,9 @@ def __getitem__(self, i):
160160
raise StopIteration
161161
def __len__(self):
162162
return 5
163-
for data in 'abc', range(5), tuple(enumerate('abc')), A(), range(1,17,5):
163+
for data in ('abc', range(5), tuple(enumerate('abc')), A(),
164+
range(1,17,5), dict.fromkeys('abcde')):
164165
self.assertEqual(list(data)[::-1], list(reversed(data)))
165-
self.assertRaises(TypeError, reversed, {})
166166
# don't allow keyword arguments
167167
self.assertRaises(TypeError, reversed, [], a=1)
168168

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make dict and dict views reversible. Patch by Rémi Lapeyre.

Objects/clinic/dictobject.c.h

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)