Почему объекты совпадения регулярных выражений не повторяются, даже если они реализуют __getitem__?

Как вы знаете, реализация метода __getitem__ делает класс итерабельным:

class IterableDemo:
    def __getitem__(self, index):
        if index > 3:
            raise IndexError

        return index

demo = IterableDemo()
print(demo[2])  # 2
print(list(demo))  # [0, 1, 2, 3]
print(hasattr(demo, '__iter__'))  # False

Однако это не относится к объектам совпадения регулярных выражений:

>>> import re
>>> match = re.match('(ab)c', 'abc')
>>> match[0]
'abc'
>>> match[1]
'ab'
>>> list(match)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '_sre.SRE_Match' object is not iterable

Стоит отметить, что это исключение не __iter__ методе __iter__, потому что этот метод даже не реализован:

>>> hasattr(match, '__iter__')
False

Итак, как можно реализовать __getitem__ не делая класс итерабельным?

Ответ 1

Есть ложь, проклятая ложь, а затем есть документация на Python.

Для __getitem__ для класса, реализованного в C, недостаточно, чтобы он был итерируемым. Это потому, что в PyTypeObject есть фактически 2 места, где __getitem__ можно сопоставить с: tp_as_sequence и tp_as_mapping. Оба имеют слот для __getitem__ ([1], [2]).

Рассматривая источник SRE_Match, tp_as_sequence инициализируется NULL тогда как tp_as_mapping определяется.

Встроенная функция iter(), если вызывается с одним аргументом, вызовет PyObject_GetIter, который имеет следующий код:

f = t->tp_iter;
if (f == NULL) {
    if (PySequence_Check(o))
        return PySeqIter_New(o);
    return type_error("'%.200s' object is not iterable", o);
}

Сначала он проверяет слот tp_iter (очевидно, NULL для объектов _SRE_Match); и если это PySequence_Check, то, если PySequence_Check вернет true, будет создан новый итератор последовательности, иначе будет создан TypeError.

PySequenceCheck сначала проверяет, является ли объект dict или подклассом dict и возвращает false в этом случае. В противном случае он возвращает значение

s->ob_type->tp_as_sequence &&
    s->ob_type->tp_as_sequence->sq_item != NULL;

и поскольку s->ob_type->tp_as_sequence был NULL для экземпляра _SRE_Match, 0 будет возвращен, а PyObject_GetIter вызывает TypeError: '_sre.SRE_Match' object is not iterable.