В Python протокол 'Sequence' определяет интерфейс для объектов, которые можно индексировать и итерировать, например, списки или кортежи. Исторически, последовательности появились практически с первых версий Python для поддержки натуральных операций со структурами данных: индексирование, срезы, перебор элементов.
Проблема — чтобы пользовательский класс вел себя как последовательность, одних только методов iter и next недостаточно. Для полной поддержки sequence behavior необходимы дополнительные методы.
Решение — для реализации собственного типа-последовательности надо определить методы getitem (необходим для индексирования и срезов) и опционально len (для len() и проверки длины). Так объект будет поддерживать итерирование, доступ по индексу, работу со срезами, а также множество стандартных операций Python с последовательностями.
Пример кода:
class MyCounter: def __init__(self, stop): self._stop = stop def __getitem__(self, index): if 0 <= index < self._stop: return index * 10 else: raise IndexError('Out of range') def __len__(self): return self._stop c = MyCounter(5) print(c[3]) # 30 print(len(c)) # 5 for x in c: print(x)
Ключевые особенности:
Если я реализую только iter и next, мой объект станет последовательностью (Sequence)?
Нет. Такой объект будет только итерируемым (iterable), но не последовательностью. Он не поддержит индексирование, срезы, стандартные функции list-like объектов.
Обязательно ли реализовывать getitem для поддержки цикла for?
Не обязательно. Если реализован iter, for будет работать. Но если нет iter, интерпретатор попробует использовать getitem, начиная с индекса 0, пока не возникнет IndexError. Поэтому для sequence достаточно getitem.
Можно ли реализовать getitem только для int, а не для slice?
Технически объект будет работать для c[0], но попытка взять срез c[1:4] сломается. Чтобы поддерживать срезы, getitem должен уметь обрабатывать объекты типа slice (смотрите slice.indices и isinstance(key, slice)).
Пример кода:
class S: def __getitem__(self, idx): if isinstance(idx, slice): return [x for x in range(idx.start or 0, idx.stop or 10, idx.step or 1)] return idx * 2
Реализовали кастомную структуру, определив только iter, думая, что теперь можно использовать срезы и индексацию.
Плюсы:
Минусы:
Класс реализует getitem с поддержкой работы срезов и int-индексов, а также len.
Плюсы:
Минусы: