mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
bpo-33462: Add __reversed__ to dict and dict views (GH-6827)
This commit is contained in:
parent
16c8a53490
commit
6531bf6309
10 changed files with 346 additions and 24 deletions
|
@ -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
|
LIFO order is now guaranteed. In prior versions, :meth:`popitem` would
|
||||||
return an arbitrary key/value pair.
|
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())``.
|
||||||
|
|
||||||
.. method:: setdefault(key[, default])
|
.. method:: setdefault(key[, default])
|
||||||
|
|
||||||
If *key* is in the dictionary, return its value. If not, insert *key*
|
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:
|
||||||
Dictionary order is guaranteed to be insertion order. This behavior was
|
Dictionary order is guaranteed to be insertion order. This behavior was
|
||||||
implementation detail of CPython from 3.6.
|
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::
|
.. seealso::
|
||||||
:class:`types.MappingProxyType` can be used to create a read-only view
|
:class:`types.MappingProxyType` can be used to create a read-only view
|
||||||
of a :class:`dict`.
|
of a :class:`dict`.
|
||||||
|
@ -4375,6 +4396,14 @@ support membership tests:
|
||||||
Return ``True`` if *x* is in the underlying dictionary's keys, values or
|
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).
|
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
|
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,
|
values are hashable, so that ``(key, value)`` pairs are unique and hashable,
|
||||||
|
|
|
@ -98,6 +98,9 @@ Other Language Changes
|
||||||
* Added support of ``\N{name}`` escapes in :mod:`regular expressions <re>`.
|
* Added support of ``\N{name}`` escapes in :mod:`regular expressions <re>`.
|
||||||
(Contributed by Jonathan Eunice and Serhiy Storchaka in :issue:`30688`.)
|
(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
|
* The syntax allowed for keyword names in function calls was further
|
||||||
restricted. In particular, ``f((keyword)=arg)`` is no longer allowed. It was
|
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
|
never intended to permit more than a bare name on the left-hand side of a
|
||||||
|
|
|
@ -51,6 +51,9 @@ PyAPI_DATA(PyTypeObject) PyDict_Type;
|
||||||
PyAPI_DATA(PyTypeObject) PyDictIterKey_Type;
|
PyAPI_DATA(PyTypeObject) PyDictIterKey_Type;
|
||||||
PyAPI_DATA(PyTypeObject) PyDictIterValue_Type;
|
PyAPI_DATA(PyTypeObject) PyDictIterValue_Type;
|
||||||
PyAPI_DATA(PyTypeObject) PyDictIterItem_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) PyDictKeys_Type;
|
||||||
PyAPI_DATA(PyTypeObject) PyDictItems_Type;
|
PyAPI_DATA(PyTypeObject) PyDictItems_Type;
|
||||||
PyAPI_DATA(PyTypeObject) PyDictValues_Type;
|
PyAPI_DATA(PyTypeObject) PyDictValues_Type;
|
||||||
|
|
|
@ -796,22 +796,21 @@ class TestOneTrickPonyABCs(ABCTestCase):
|
||||||
|
|
||||||
def test_Reversible(self):
|
def test_Reversible(self):
|
||||||
# Check some non-reversibles
|
# 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:
|
for x in non_samples:
|
||||||
self.assertNotIsInstance(x, Reversible)
|
self.assertNotIsInstance(x, Reversible)
|
||||||
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
|
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
|
||||||
# Check some non-reversible iterables
|
# Check some non-reversible iterables
|
||||||
non_reversibles = [dict().keys(), dict().items(), dict().values(),
|
non_reversibles = [_test_gen(), (x for x in []), iter([]), reversed([])]
|
||||||
Counter(), Counter().keys(), Counter().items(),
|
|
||||||
Counter().values(), _test_gen(),
|
|
||||||
(x for x in []), iter([]), reversed([])]
|
|
||||||
for x in non_reversibles:
|
for x in non_reversibles:
|
||||||
self.assertNotIsInstance(x, Reversible)
|
self.assertNotIsInstance(x, Reversible)
|
||||||
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
|
self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
|
||||||
# Check some reversible iterables
|
# Check some reversible iterables
|
||||||
samples = [bytes(), str(), tuple(), list(), OrderedDict(),
|
samples = [bytes(), str(), tuple(), list(), OrderedDict(),
|
||||||
OrderedDict().keys(), OrderedDict().items(),
|
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:
|
for x in samples:
|
||||||
self.assertIsInstance(x, Reversible)
|
self.assertIsInstance(x, Reversible)
|
||||||
self.assertTrue(issubclass(type(x), Reversible), repr(type(x)))
|
self.assertTrue(issubclass(type(x), Reversible), repr(type(x)))
|
||||||
|
@ -1612,7 +1611,7 @@ class TestCollectionABCs(ABCTestCase):
|
||||||
self.assertIsInstance(z, set)
|
self.assertIsInstance(z, set)
|
||||||
list(z)
|
list(z)
|
||||||
mymap['blue'] = 7 # Shouldn't affect '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):
|
def test_Sequence(self):
|
||||||
for sample in [tuple, list, bytes, str]:
|
for sample in [tuple, list, bytes, str]:
|
||||||
|
@ -1767,10 +1766,10 @@ class TestCounter(unittest.TestCase):
|
||||||
self.assertTrue(issubclass(Counter, Mapping))
|
self.assertTrue(issubclass(Counter, Mapping))
|
||||||
self.assertEqual(len(c), 3)
|
self.assertEqual(len(c), 3)
|
||||||
self.assertEqual(sum(c.values()), 6)
|
self.assertEqual(sum(c.values()), 6)
|
||||||
self.assertEqual(sorted(c.values()), [1, 2, 3])
|
self.assertEqual(list(c.values()), [3, 2, 1])
|
||||||
self.assertEqual(sorted(c.keys()), ['a', 'b', 'c'])
|
self.assertEqual(list(c.keys()), ['a', 'b', 'c'])
|
||||||
self.assertEqual(sorted(c), ['a', 'b', 'c'])
|
self.assertEqual(list(c), ['a', 'b', 'c'])
|
||||||
self.assertEqual(sorted(c.items()),
|
self.assertEqual(list(c.items()),
|
||||||
[('a', 3), ('b', 2), ('c', 1)])
|
[('a', 3), ('b', 2), ('c', 1)])
|
||||||
self.assertEqual(c['b'], 2)
|
self.assertEqual(c['b'], 2)
|
||||||
self.assertEqual(c['z'], 0)
|
self.assertEqual(c['z'], 0)
|
||||||
|
@ -1784,7 +1783,7 @@ class TestCounter(unittest.TestCase):
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
self.assertEqual(c.most_common(i),
|
self.assertEqual(c.most_common(i),
|
||||||
[('a', 3), ('b', 2), ('c', 1)][: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['a'] += 1 # increment an existing value
|
||||||
c['b'] -= 2 # sub existing value to zero
|
c['b'] -= 2 # sub existing value to zero
|
||||||
del c['c'] # remove an entry
|
del c['c'] # remove an entry
|
||||||
|
@ -1793,7 +1792,7 @@ class TestCounter(unittest.TestCase):
|
||||||
c['e'] = -5 # directly assign a missing value
|
c['e'] = -5 # directly assign a missing value
|
||||||
c['f'] += 4 # add to 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(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.assertEqual(c.pop('f'), 4)
|
||||||
self.assertNotIn('f', c)
|
self.assertNotIn('f', c)
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
|
|
|
@ -1021,7 +1021,7 @@ class DictTest(unittest.TestCase):
|
||||||
it = iter(data)
|
it = iter(data)
|
||||||
d = pickle.dumps(it, proto)
|
d = pickle.dumps(it, proto)
|
||||||
it = pickle.loads(d)
|
it = pickle.loads(d)
|
||||||
self.assertEqual(sorted(it), sorted(data))
|
self.assertEqual(list(it), list(data))
|
||||||
|
|
||||||
it = pickle.loads(d)
|
it = pickle.loads(d)
|
||||||
try:
|
try:
|
||||||
|
@ -1031,7 +1031,7 @@ class DictTest(unittest.TestCase):
|
||||||
d = pickle.dumps(it, proto)
|
d = pickle.dumps(it, proto)
|
||||||
it = pickle.loads(d)
|
it = pickle.loads(d)
|
||||||
del data[drop]
|
del data[drop]
|
||||||
self.assertEqual(sorted(it), sorted(data))
|
self.assertEqual(list(it), list(data))
|
||||||
|
|
||||||
def test_itemiterator_pickling(self):
|
def test_itemiterator_pickling(self):
|
||||||
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||||
|
@ -1062,7 +1062,7 @@ class DictTest(unittest.TestCase):
|
||||||
it = iter(data.values())
|
it = iter(data.values())
|
||||||
d = pickle.dumps(it, proto)
|
d = pickle.dumps(it, proto)
|
||||||
it = pickle.loads(d)
|
it = pickle.loads(d)
|
||||||
self.assertEqual(sorted(list(it)), sorted(list(data.values())))
|
self.assertEqual(list(it), list(data.values()))
|
||||||
|
|
||||||
it = pickle.loads(d)
|
it = pickle.loads(d)
|
||||||
drop = next(it)
|
drop = next(it)
|
||||||
|
@ -1071,6 +1071,62 @@ class DictTest(unittest.TestCase):
|
||||||
values = list(it) + [drop]
|
values = list(it) + [drop]
|
||||||
self.assertEqual(sorted(values), sorted(list(data.values())))
|
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):
|
def test_instance_dict_getattr_str_subclass(self):
|
||||||
class Foo:
|
class Foo:
|
||||||
def __init__(self, msg):
|
def __init__(self, msg):
|
||||||
|
@ -1222,6 +1278,13 @@ class DictTest(unittest.TestCase):
|
||||||
|
|
||||||
self.assertRaises(RuntimeError, 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):
|
def test_dict_copy_order(self):
|
||||||
# bpo-34320
|
# bpo-34320
|
||||||
od = collections.OrderedDict([('a', 1), ('b', 2)])
|
od = collections.OrderedDict([('a', 1), ('b', 2)])
|
||||||
|
|
|
@ -160,9 +160,9 @@ class TestReversed(unittest.TestCase, PickleTest):
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return 5
|
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.assertEqual(list(data)[::-1], list(reversed(data)))
|
||||||
self.assertRaises(TypeError, reversed, {})
|
|
||||||
# don't allow keyword arguments
|
# don't allow keyword arguments
|
||||||
self.assertRaises(TypeError, reversed, [], a=1)
|
self.assertRaises(TypeError, reversed, [], a=1)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Make dict and dict views reversible. Patch by Rémi Lapeyre.
|
20
Objects/clinic/dictobject.c.h
generated
20
Objects/clinic/dictobject.c.h
generated
|
@ -103,4 +103,22 @@ dict_setdefault(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs)
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=d7508c5091609a23 input=a9049054013a1b77]*/
|
|
||||||
|
PyDoc_STRVAR(dict___reversed____doc__,
|
||||||
|
"__reversed__($self, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Return a reverse iterator over the dict keys.");
|
||||||
|
|
||||||
|
#define DICT___REVERSED___METHODDEF \
|
||||||
|
{"__reversed__", (PyCFunction)dict___reversed__, METH_NOARGS, dict___reversed____doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
dict___reversed___impl(PyDictObject *self);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
dict___reversed__(PyDictObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
return dict___reversed___impl(self);
|
||||||
|
}
|
||||||
|
/*[clinic end generated code: output=b9923851cbd9213a input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -3100,6 +3100,7 @@ static PyMethodDef mapp_methods[] = {
|
||||||
clear__doc__},
|
clear__doc__},
|
||||||
{"copy", (PyCFunction)dict_copy, METH_NOARGS,
|
{"copy", (PyCFunction)dict_copy, METH_NOARGS,
|
||||||
copy__doc__},
|
copy__doc__},
|
||||||
|
DICT___REVERSED___METHODDEF
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3335,22 +3336,32 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype)
|
||||||
{
|
{
|
||||||
dictiterobject *di;
|
dictiterobject *di;
|
||||||
di = PyObject_GC_New(dictiterobject, itertype);
|
di = PyObject_GC_New(dictiterobject, itertype);
|
||||||
if (di == NULL)
|
if (di == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
Py_INCREF(dict);
|
Py_INCREF(dict);
|
||||||
di->di_dict = dict;
|
di->di_dict = dict;
|
||||||
di->di_used = dict->ma_used;
|
di->di_used = dict->ma_used;
|
||||||
di->di_pos = 0;
|
|
||||||
di->len = dict->ma_used;
|
di->len = dict->ma_used;
|
||||||
if (itertype == &PyDictIterItem_Type) {
|
if ((itertype == &PyDictRevIterKey_Type ||
|
||||||
|
itertype == &PyDictRevIterItem_Type ||
|
||||||
|
itertype == &PyDictRevIterValue_Type) && dict->ma_used) {
|
||||||
|
di->di_pos = dict->ma_keys->dk_nentries - 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
di->di_pos = 0;
|
||||||
|
}
|
||||||
|
if (itertype == &PyDictIterItem_Type ||
|
||||||
|
itertype == &PyDictRevIterItem_Type) {
|
||||||
di->di_result = PyTuple_Pack(2, Py_None, Py_None);
|
di->di_result = PyTuple_Pack(2, Py_None, Py_None);
|
||||||
if (di->di_result == NULL) {
|
if (di->di_result == NULL) {
|
||||||
Py_DECREF(di);
|
Py_DECREF(di);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
di->di_result = NULL;
|
di->di_result = NULL;
|
||||||
|
}
|
||||||
_PyObject_GC_TRACK(di);
|
_PyObject_GC_TRACK(di);
|
||||||
return (PyObject *)di;
|
return (PyObject *)di;
|
||||||
}
|
}
|
||||||
|
@ -3664,6 +3675,120 @@ PyTypeObject PyDictIterItem_Type = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* dictreviter */
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
dictreviter_iternext(dictiterobject *di)
|
||||||
|
{
|
||||||
|
PyDictObject *d = di->di_dict;
|
||||||
|
|
||||||
|
if (d == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
assert (PyDict_Check(d));
|
||||||
|
|
||||||
|
if (di->di_used != d->ma_used) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
|
"dictionary changed size during iteration");
|
||||||
|
di->di_used = -1; /* Make this state sticky */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t i = di->di_pos;
|
||||||
|
PyDictKeysObject *k = d->ma_keys;
|
||||||
|
PyObject *key, *value, *result;
|
||||||
|
|
||||||
|
if (d->ma_values) {
|
||||||
|
if (i < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
key = DK_ENTRIES(k)[i].me_key;
|
||||||
|
value = d->ma_values[i];
|
||||||
|
assert (value != NULL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyDictKeyEntry *entry_ptr = &DK_ENTRIES(k)[i];
|
||||||
|
while (i >= 0 && entry_ptr->me_value == NULL) {
|
||||||
|
entry_ptr--;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
if (i < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
key = entry_ptr->me_key;
|
||||||
|
value = entry_ptr->me_value;
|
||||||
|
}
|
||||||
|
di->di_pos = i-1;
|
||||||
|
di->len--;
|
||||||
|
|
||||||
|
if (Py_TYPE(di) == &PyDictRevIterKey_Type) {
|
||||||
|
Py_INCREF(key);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
else if (Py_TYPE(di) == &PyDictRevIterValue_Type) {
|
||||||
|
Py_INCREF(value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
else if (Py_TYPE(di) == &PyDictRevIterItem_Type) {
|
||||||
|
Py_INCREF(key);
|
||||||
|
Py_INCREF(value);
|
||||||
|
result = di->di_result;
|
||||||
|
if (Py_REFCNT(result) == 1) {
|
||||||
|
PyObject *oldkey = PyTuple_GET_ITEM(result, 0);
|
||||||
|
PyObject *oldvalue = PyTuple_GET_ITEM(result, 1);
|
||||||
|
PyTuple_SET_ITEM(result, 0, key); /* steals reference */
|
||||||
|
PyTuple_SET_ITEM(result, 1, value); /* steals reference */
|
||||||
|
Py_INCREF(result);
|
||||||
|
Py_DECREF(oldkey);
|
||||||
|
Py_DECREF(oldvalue);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = PyTuple_New(2);
|
||||||
|
if (result == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyTuple_SET_ITEM(result, 0, key); /* steals reference */
|
||||||
|
PyTuple_SET_ITEM(result, 1, value); /* steals reference */
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Py_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
fail:
|
||||||
|
di->di_dict = NULL;
|
||||||
|
Py_DECREF(d);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyTypeObject PyDictRevIterKey_Type = {
|
||||||
|
PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
||||||
|
"dict_reversekeyiterator",
|
||||||
|
sizeof(dictiterobject),
|
||||||
|
.tp_dealloc = (destructor)dictiter_dealloc,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
|
||||||
|
.tp_traverse = (traverseproc)dictiter_traverse,
|
||||||
|
.tp_iter = PyObject_SelfIter,
|
||||||
|
.tp_iternext = (iternextfunc)dictreviter_iternext,
|
||||||
|
.tp_methods = dictiter_methods
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
dict.__reversed__
|
||||||
|
|
||||||
|
Return a reverse iterator over the dict keys.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
dict___reversed___impl(PyDictObject *self)
|
||||||
|
/*[clinic end generated code: output=e674483336d1ed51 input=23210ef3477d8c4d]*/
|
||||||
|
{
|
||||||
|
assert (PyDict_Check(self));
|
||||||
|
return dictiter_new(self, &PyDictRevIterKey_Type);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored))
|
dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored))
|
||||||
{
|
{
|
||||||
|
@ -3671,7 +3796,6 @@ dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored))
|
||||||
dictiterobject tmp = *di;
|
dictiterobject tmp = *di;
|
||||||
Py_XINCREF(tmp.di_dict);
|
Py_XINCREF(tmp.di_dict);
|
||||||
|
|
||||||
/* iterate the temporary into a list */
|
|
||||||
PyObject *list = PySequence_List((PyObject*)&tmp);
|
PyObject *list = PySequence_List((PyObject*)&tmp);
|
||||||
Py_XDECREF(tmp.di_dict);
|
Py_XDECREF(tmp.di_dict);
|
||||||
if (list == NULL) {
|
if (list == NULL) {
|
||||||
|
@ -3680,6 +3804,30 @@ dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored))
|
||||||
return Py_BuildValue("N(N)", _PyObject_GetBuiltin("iter"), list);
|
return Py_BuildValue("N(N)", _PyObject_GetBuiltin("iter"), list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyTypeObject PyDictRevIterItem_Type = {
|
||||||
|
PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
||||||
|
"dict_reverseitemiterator",
|
||||||
|
sizeof(dictiterobject),
|
||||||
|
.tp_dealloc = (destructor)dictiter_dealloc,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
|
||||||
|
.tp_traverse = (traverseproc)dictiter_traverse,
|
||||||
|
.tp_iter = PyObject_SelfIter,
|
||||||
|
.tp_iternext = (iternextfunc)dictreviter_iternext,
|
||||||
|
.tp_methods = dictiter_methods
|
||||||
|
};
|
||||||
|
|
||||||
|
PyTypeObject PyDictRevIterValue_Type = {
|
||||||
|
PyVarObject_HEAD_INIT(&PyType_Type, 0)
|
||||||
|
"dict_reversevalueiterator",
|
||||||
|
sizeof(dictiterobject),
|
||||||
|
.tp_dealloc = (destructor)dictiter_dealloc,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
|
||||||
|
.tp_traverse = (traverseproc)dictiter_traverse,
|
||||||
|
.tp_iter = PyObject_SelfIter,
|
||||||
|
.tp_iternext = (iternextfunc)dictreviter_iternext,
|
||||||
|
.tp_methods = dictiter_methods
|
||||||
|
};
|
||||||
|
|
||||||
/***********************************************/
|
/***********************************************/
|
||||||
/* View objects for keys(), items(), values(). */
|
/* View objects for keys(), items(), values(). */
|
||||||
/***********************************************/
|
/***********************************************/
|
||||||
|
@ -4035,9 +4183,16 @@ dictviews_isdisjoint(PyObject *self, PyObject *other)
|
||||||
PyDoc_STRVAR(isdisjoint_doc,
|
PyDoc_STRVAR(isdisjoint_doc,
|
||||||
"Return True if the view and the given iterable have a null intersection.");
|
"Return True if the view and the given iterable have a null intersection.");
|
||||||
|
|
||||||
|
static PyObject* dictkeys_reversed(_PyDictViewObject *dv);
|
||||||
|
|
||||||
|
PyDoc_STRVAR(reversed_keys_doc,
|
||||||
|
"Return a reverse iterator over the dict keys.");
|
||||||
|
|
||||||
static PyMethodDef dictkeys_methods[] = {
|
static PyMethodDef dictkeys_methods[] = {
|
||||||
{"isdisjoint", (PyCFunction)dictviews_isdisjoint, METH_O,
|
{"isdisjoint", (PyCFunction)dictviews_isdisjoint, METH_O,
|
||||||
isdisjoint_doc},
|
isdisjoint_doc},
|
||||||
|
{"__reversed__", (PyCFunction)dictkeys_reversed, METH_NOARGS,
|
||||||
|
reversed_keys_doc},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4080,6 +4235,15 @@ dictkeys_new(PyObject *dict, PyObject *Py_UNUSED(ignored))
|
||||||
return _PyDictView_New(dict, &PyDictKeys_Type);
|
return _PyDictView_New(dict, &PyDictKeys_Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
dictkeys_reversed(_PyDictViewObject *dv)
|
||||||
|
{
|
||||||
|
if (dv->dv_dict == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
return dictiter_new(dv->dv_dict, &PyDictRevIterKey_Type);
|
||||||
|
}
|
||||||
|
|
||||||
/*** dict_items ***/
|
/*** dict_items ***/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -4125,9 +4289,16 @@ static PySequenceMethods dictitems_as_sequence = {
|
||||||
(objobjproc)dictitems_contains, /* sq_contains */
|
(objobjproc)dictitems_contains, /* sq_contains */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PyObject* dictitems_reversed(_PyDictViewObject *dv);
|
||||||
|
|
||||||
|
PyDoc_STRVAR(reversed_items_doc,
|
||||||
|
"Return a reverse iterator over the dict items.");
|
||||||
|
|
||||||
static PyMethodDef dictitems_methods[] = {
|
static PyMethodDef dictitems_methods[] = {
|
||||||
{"isdisjoint", (PyCFunction)dictviews_isdisjoint, METH_O,
|
{"isdisjoint", (PyCFunction)dictviews_isdisjoint, METH_O,
|
||||||
isdisjoint_doc},
|
isdisjoint_doc},
|
||||||
|
{"__reversed__", (PyCFunction)dictitems_reversed, METH_NOARGS,
|
||||||
|
reversed_items_doc},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4170,6 +4341,15 @@ dictitems_new(PyObject *dict, PyObject *Py_UNUSED(ignored))
|
||||||
return _PyDictView_New(dict, &PyDictItems_Type);
|
return _PyDictView_New(dict, &PyDictItems_Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
dictitems_reversed(_PyDictViewObject *dv)
|
||||||
|
{
|
||||||
|
if (dv->dv_dict == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
return dictiter_new(dv->dv_dict, &PyDictRevIterItem_Type);
|
||||||
|
}
|
||||||
|
|
||||||
/*** dict_values ***/
|
/*** dict_values ***/
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -4192,7 +4372,14 @@ static PySequenceMethods dictvalues_as_sequence = {
|
||||||
(objobjproc)0, /* sq_contains */
|
(objobjproc)0, /* sq_contains */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PyObject* dictvalues_reversed(_PyDictViewObject *dv);
|
||||||
|
|
||||||
|
PyDoc_STRVAR(reversed_values_doc,
|
||||||
|
"Return a reverse iterator over the dict values.");
|
||||||
|
|
||||||
static PyMethodDef dictvalues_methods[] = {
|
static PyMethodDef dictvalues_methods[] = {
|
||||||
|
{"__reversed__", (PyCFunction)dictvalues_reversed, METH_NOARGS,
|
||||||
|
reversed_values_doc},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4235,6 +4422,16 @@ dictvalues_new(PyObject *dict, PyObject *Py_UNUSED(ignored))
|
||||||
return _PyDictView_New(dict, &PyDictValues_Type);
|
return _PyDictView_New(dict, &PyDictValues_Type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
dictvalues_reversed(_PyDictViewObject *dv)
|
||||||
|
{
|
||||||
|
if (dv->dv_dict == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
return dictiter_new(dv->dv_dict, &PyDictRevIterValue_Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Returns NULL if cannot allocate a new PyDictKeysObject,
|
/* Returns NULL if cannot allocate a new PyDictKeysObject,
|
||||||
but does not set an error */
|
but does not set an error */
|
||||||
PyDictKeysObject *
|
PyDictKeysObject *
|
||||||
|
|
|
@ -1790,6 +1790,15 @@ _Py_ReadyTypes(void)
|
||||||
if (PyType_Ready(&PyDictItems_Type) < 0)
|
if (PyType_Ready(&PyDictItems_Type) < 0)
|
||||||
Py_FatalError("Can't initialize dict items type");
|
Py_FatalError("Can't initialize dict items type");
|
||||||
|
|
||||||
|
if (PyType_Ready(&PyDictRevIterKey_Type) < 0)
|
||||||
|
Py_FatalError("Can't initialize reversed dict keys type");
|
||||||
|
|
||||||
|
if (PyType_Ready(&PyDictRevIterValue_Type) < 0)
|
||||||
|
Py_FatalError("Can't initialize reversed dict values type");
|
||||||
|
|
||||||
|
if (PyType_Ready(&PyDictRevIterItem_Type) < 0)
|
||||||
|
Py_FatalError("Can't initialize reversed dict items type");
|
||||||
|
|
||||||
if (PyType_Ready(&PyODict_Type) < 0)
|
if (PyType_Ready(&PyODict_Type) < 0)
|
||||||
Py_FatalError("Can't initialize OrderedDict type");
|
Py_FatalError("Can't initialize OrderedDict type");
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue