According to Python’s docs, reversed()
uses __getitem__
and __len__
if __reversed__
is not implemented.
I’ve encountered a weird behavior and failed to explain it:
>>> class A(dict): ... pass ... >>> reversed(A()) Traceback (most recent call last): ... TypeError: 'A' object is not reversible >>> class B(dict): ... def __getitem__(self, key): ... return super().__getitem__(key) ... def __len__(self): ... return super().__len__() ... >>> reversed(B()) Traceback (most recent call last): ... TypeError: 'B' object is not reversible >>> class C: ... def __getitem__(self, key): ... return "item" ... def __len__(self): ... return 1 ... >>> reversed(C()) <reversed object at 0x00000000022BB9B0>
Although calling reversed()
on mapping types makes no sense, how does it know it’s a mapping? Does it internally check isinstance(inst, dict)
? Does it check for any general mapping like collections.abc.Mapping
? Is there any way to override this behavior without implementing __reversed__
?
I thought it might be due to dict
implementing a __reversed__
that throws a TypeError
, or one that equals None
much like how you disable __hash__
, but dict.__reversed__
turned out empty with AttributeError
thrown.
UPDATE:
New Python versions implement __reversed__
for dictionaries. Mapping protocols (such as collections.abc.Mapping) set __reversed__
to None
.
Advertisement
Answer
Yes, there’s a check for dict
type in PySequence_Check
used by reversed
.
// cpython/Objects/enumobject.c if (!PySequence_Check(seq)) { PyErr_Format(PyExc_TypeError, "'%.200s' object is not reversible", Py_TYPE(seq)->tp_name); return NULL; } // cpython/Objects/abstract.c int PySequence_Check(PyObject *s) { if (PyDict_Check(s)) return 0; return s != NULL && s->ob_type->tp_as_sequence && s->ob_type->tp_as_sequence->sq_item != NULL; }