ПрограммированиеBackend разработчик

Объясните протокол 'Sequence' в Python, как его реализовать и чем он отличается от просто итерируемых объектов?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

В 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)

Ключевые особенности:

  • Объект поддерживает доступ по индексу и срезы через getitem.
  • Для поддержки функций len() нужен len.
  • Итерация строится на getitem, а не на iter, если iter не определён.

Вопросы с подвохом.

Если я реализую только 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, ожидая, что object станет полноценной последовательностью.
  • Реализуют getitem, но не обрабатывают slice-объекты.
  • Не реализуют len, из-за чего len(obj) вызывает TypeError.

Пример из жизни

Негативный кейс

Реализовали кастомную структуру, определив только iter, думая, что теперь можно использовать срезы и индексацию.

Плюсы:

  • Работает в цикле for и поддерживает генераторы.

Минусы:

  • Нет поддержки синтаксиса obj[5] или obj[1:3], падает с ошибкой.
  • len(obj) тоже не работает.

Позитивный кейс

Класс реализует getitem с поддержкой работы срезов и int-индексов, а также len.

Плюсы:

  • Поддерживаются все операции последовательности: срезы, индексация, len, итерация.
  • Интеграция со стандартной библиотекой (например, random.choice).

Минусы:

  • Нужно внимательно реализовывать обработку slice, иначе возможны баги при работе со срезами.