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