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.
New Python versions implement __reversed__
for dictionaries. Mapping protocols (such as collections.abc.Mapping) set __reversed__
to None
.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With