ПрограммированиеРазработчик библиотек Python

Как работает метод __getitem__ в Python, зачем его реализовывать и что нужно учитывать при обработке срезов (slice)?

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

Ответ.

История вопроса

Метод getitem был добавлен в Python как часть протокола последовательностей и отображений. С помощью этого магического метода стандартные коллекции (list, tuple, dict и др.) поддерживают доступ по индексу, ключу и срезу. В пользовательских классах этот метод позволяет объектам вести себя "как коллекции".

Проблема

Без реализации getitem пользовательский класс не поддерживает индексирование и перебор по for. Реализация только для простых индексов неприменима, если хотим полноценную работу со срезами (slice), а игнорирование разновидностей индекса приведёт к ошибкам.

Решение

Реализовать getitem для поддержки и индексов, и объектов типа slice, разделяя их в обработке. Это позволяет использовать пользовательские объекты в стандартных конструкциях Python (например, для поддержки срезов your_obj[1:5]).

Пример кода:

class MyRange: def __init__(self, n): self.n = n def __getitem__(self, item): if isinstance(item, int): # Индивид. индекс if 0 <= item < self.n: return item * 2 raise IndexError('index out of range') elif isinstance(item, slice): # Срез return [self[i] for i in range(*item.indices(self.n))] else: raise TypeError('Invalid argument type: {}'.format(type(item))) mr = MyRange(10) print(mr[3]) # 6 print(mr[2:5]) # [4, 6, 8]

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

  • getitem — основа протокола последовательностей и интерабельности
  • Для поддержки срезов нужно обрабатывать slice отдельно
  • Исключения IndexError и TypeError нужны для корректной работы стандартных функций (например, list(), for)

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

Обеспечивает ли реализация только getitem возможность присваивать значения по индексу?

Нет. Для поддержки присваивания (your_obj[i] = value) нужно реализовать setitem. getitem отвечает только за чтение.

Должен ли getitem обязательно отдавать список на срез, как list?

Нет. Главное — возвращать "последовательность" с учетом смысла класса (можно возвращать тот же тип или, к примеру, tuple). Главное, чтобы это было осмысленно в контексте задачи.

Почему иногда возникает ошибка TypeError: 'MyClass' object is not subscriptable?

Такое сообщение появляется, если вы пробуете выполнить my_obj[0], а класс не реализует getitem. Чтобы класс был subscriptable (поддерживал []), этот метод обязательный.

Типовые ошибки и анти-паттерны

  • Проверяют только int, игнорируя slice: срезы всегда падают с ошибкой
  • Возвращают неверный тип (например, None при срезах вместо последовательности)
  • Не выбрасывают IndexError, из-за чего цикл for ведет себя некорректно

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

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

Реализовали getitem только для int, забыли про slice. Любая попытка my_obj[2:5] приводит к TypeError и сбою всего алгоритма обработки коллекций.

Плюсы:

  • Простота

Минусы:

  • Не поддерживаются срезы, код несовместим с большинством стандартных конструкций

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

getitem реализован с отдельной обработкой slice и int. Срезы и индексы работают, методы list(), map(), итерирования поддерживаются без дополнительных усилий.

Плюсы:

  • Класс совместим со стандартными инструментами Python
  • Легко расширяется под любые типы индексов (tuple, str для матриц, и т. д.)

Минусы:

  • Реализация требует чуть больше кода и тестирования