diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 4686cec9688..49d433805a3 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -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())``. + .. method:: setdefault(key[, default]) 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 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`. @@ -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, diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 566c369c85b..2d3a116df9d 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -98,6 +98,9 @@ Other Language Changes * Added support of ``\N{name}`` escapes in :mod:`regular expressions `. (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 diff --git a/Include/dictobject.h b/Include/dictobject.h index 28930f436d7..c0f24df7d26 100644 --- a/Include/dictobject.h +++ b/Include/dictobject.h @@ -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; diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 0b7cb5848b1..200e0273c33 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -796,22 +796,21 @@ class TestOneTrickPonyABCs(ABCTestCase): 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))) @@ -1612,7 +1611,7 @@ class TestCollectionABCs(ABCTestCase): 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]: @@ -1767,10 +1766,10 @@ class TestCounter(unittest.TestCase): 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) @@ -1784,7 +1783,7 @@ class TestCounter(unittest.TestCase): 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 @@ -1793,7 +1792,7 @@ class TestCounter(unittest.TestCase): 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): diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 90c0a3131a7..71fffe398f3 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1021,7 +1021,7 @@ class DictTest(unittest.TestCase): 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: @@ -1031,7 +1031,7 @@ class DictTest(unittest.TestCase): 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): @@ -1062,7 +1062,7 @@ class DictTest(unittest.TestCase): 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) @@ -1071,6 +1071,62 @@ class DictTest(unittest.TestCase): 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): @@ -1222,6 +1278,13 @@ class DictTest(unittest.TestCase): 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)]) diff --git a/Lib/test/test_enumerate.py b/Lib/test/test_enumerate.py index e455adee50f..5785cb46492 100644 --- a/Lib/test/test_enumerate.py +++ b/Lib/test/test_enumerate.py @@ -160,9 +160,9 @@ class TestReversed(unittest.TestCase, PickleTest): 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) diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-05-23-17-18-02.bpo-33462.gurbpbrhe.rst b/Misc/NEWS.d/next/Core and Builtins/2018-05-23-17-18-02.bpo-33462.gurbpbrhe.rst new file mode 100644 index 00000000000..ed1f0342731 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-05-23-17-18-02.bpo-33462.gurbpbrhe.rst @@ -0,0 +1 @@ +Make dict and dict views reversible. Patch by Rémi Lapeyre. diff --git a/Objects/clinic/dictobject.c.h b/Objects/clinic/dictobject.c.h index 58677cd772b..4f4c9fa9cb7 100644 --- a/Objects/clinic/dictobject.c.h +++ b/Objects/clinic/dictobject.c.h @@ -103,4 +103,22 @@ dict_setdefault(PyDictObject *self, PyObject *const *args, Py_ssize_t nargs) 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]*/ diff --git a/Objects/dictobject.c b/Objects/dictobject.c index ea564a2b7c9..08ec9e254ab 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -3100,6 +3100,7 @@ static PyMethodDef mapp_methods[] = { clear__doc__}, {"copy", (PyCFunction)dict_copy, METH_NOARGS, copy__doc__}, + DICT___REVERSED___METHODDEF {NULL, NULL} /* sentinel */ }; @@ -3335,22 +3336,32 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype) { 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; } @@ -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 * dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored)) { @@ -3671,7 +3796,6 @@ 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) { @@ -3680,6 +3804,30 @@ dictiter_reduce(dictiterobject *di, PyObject *Py_UNUSED(ignored)) 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(). */ /***********************************************/ @@ -4035,9 +4183,16 @@ dictviews_isdisjoint(PyObject *self, PyObject *other) 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 */ }; @@ -4080,6 +4235,15 @@ dictkeys_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) 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 * @@ -4125,9 +4289,16 @@ static PySequenceMethods dictitems_as_sequence = { (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 */ }; @@ -4170,6 +4341,15 @@ dictitems_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) 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 * @@ -4192,7 +4372,14 @@ static PySequenceMethods dictvalues_as_sequence = { (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 */ }; @@ -4235,6 +4422,16 @@ dictvalues_new(PyObject *dict, PyObject *Py_UNUSED(ignored)) 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 * diff --git a/Objects/object.c b/Objects/object.c index d3a97f6c5bd..72e2684820c 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1790,6 +1790,15 @@ _Py_ReadyTypes(void) 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");