Commit 6531bf63 authored by Rémi Lapeyre's avatar Rémi Lapeyre Committed by INADA Naoki

bpo-33462: Add __reversed__ to dict and dict views (GH-6827)

parent 16c8a534
...@@ -4285,6 +4285,11 @@ pairs within braces, for example: ``{'jack': 4098, 'sjoerd': 4127}`` or ``{4098: ...@@ -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: ...@@ -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: ...@@ -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 ...@@ -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; ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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)
......
Make dict and dict views reversible. Patch by Rémi Lapeyre.
...@@ -103,4 +103,22 @@ dict_setdefault(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) ...@@ -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[] = { ...@@ -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) ...@@ -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 = { ...@@ -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)) ...@@ -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)) ...@@ -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) ...@@ -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)) ...@@ -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 = { ...@@ -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)) ...@@ -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 = { ...@@ -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)) ...@@ -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) ...@@ -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");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment