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())``.
+
.. method:: setdefault(key[, default])
If *key* is in the dictionary, return its value. If not, insert *key*
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`.
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,
* 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
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;
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)))
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]:
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)
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
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):
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:
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):
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)
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):
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)])
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)
--- /dev/null
+Make dict and dict views reversible. Patch by Rémi Lapeyre.
exit:
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]*/
clear__doc__},
{"copy", (PyCFunction)dict_copy, METH_NOARGS,
copy__doc__},
+ DICT___REVERSED___METHODDEF
{NULL, NULL} /* sentinel */
};
{
dictiterobject *di;
di = PyObject_GC_New(dictiterobject, itertype);
- if (di == NULL)
+ if (di == NULL) {
return NULL;
+ }
Py_INCREF(dict);
di->di_dict = dict;
di->di_used = dict->ma_used;
- di->di_pos = 0;
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);
if (di->di_result == NULL) {
Py_DECREF(di);
return NULL;
}
}
- else
+ else {
di->di_result = NULL;
+ }
_PyObject_GC_TRACK(di);
return (PyObject *)di;
}
};
+/* 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 *
dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored))
{
dictiterobject tmp = *di;
Py_XINCREF(tmp.di_dict);
- /* iterate the temporary into a list */
PyObject *list = PySequence_List((PyObject*)&tmp);
Py_XDECREF(tmp.di_dict);
if (list == NULL) {
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(). */
/***********************************************/
PyDoc_STRVAR(isdisjoint_doc,
"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[] = {
{"isdisjoint", (PyCFunction)dictviews_isdisjoint, METH_O,
isdisjoint_doc},
+ {"__reversed__", (PyCFunction)dictkeys_reversed, METH_NOARGS,
+ reversed_keys_doc},
{NULL, NULL} /* sentinel */
};
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 ***/
static PyObject *
(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[] = {
{"isdisjoint", (PyCFunction)dictviews_isdisjoint, METH_O,
isdisjoint_doc},
+ {"__reversed__", (PyCFunction)dictitems_reversed, METH_NOARGS,
+ reversed_items_doc},
{NULL, NULL} /* sentinel */
};
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 ***/
static PyObject *
(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[] = {
+ {"__reversed__", (PyCFunction)dictvalues_reversed, METH_NOARGS,
+ reversed_values_doc},
{NULL, NULL} /* sentinel */
};
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,
but does not set an error */
PyDictKeysObject *
if (PyType_Ready(&PyDictItems_Type) < 0)
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)
Py_FatalError("Can't initialize OrderedDict type");