Класс может реализовывать определенные операции, которые вызываются специальным синтаксисом (например, арифметические операции или индексирование и срезы), определяя методы со специальными именами. Это подход Python к перегрузке операторов, позволяющий классам определять собственное поведение по отношению к операторам языка. Например, если класс определяет метод с именем __getitem__()
, а x
является экземпляром этого класса, то x[i]
примерно эквивалентен type(x).__getitem__(x, i)
. Если соответствующий метод не определен, то попытки выполнить операцию вызывают исключение (обычно AttributeError
или TypeError
).
Установка для специального метода значения None
означает, что соответствующая операция недоступна. Например, если класс устанавливает для __iter__()
значение None
, то класс не является итерируемым, поэтому вызов функции iter()
в его экземплярах вызовет ошибку TypeError
, без возврата к __getitem__()
.
При реализации класса, эмулирующего любой встроенный тип, важно, чтобы эмуляция была реализована только в той степени, в которой это имеет смысл для моделируемого объекта. Например, некоторые последовательности могут хорошо работать с извлечением отдельных элементов, но извлечение фрагмента среза может не иметь смысла. Одним из примеров этого является интерфейс NodeList
в объектной модели документа W3C.
__slots__
в классах Python;__init_subclass__
класса Python;__getitem__()
.Представьте себе класс, который моделирует здание. В рамках данных для здания он включает в себя ряд атрибутов, включая описания компаний, которые занимают каждый этаж :
Без использования специального метода __getitem__()
получился бы такой класс:
class Building(object): def __init__(self, floors): self._floors = [None]*floors def occupy(self, floor_number, data): self._floors[floor_number] = data def get_floor_data(self, floor_number): return self._floors[floor_number] # Строим здание с 4 этажами building = Building(4) building.occupy(0, 'Reception') building.occupy(1, 'ABC Corp') building.occupy(2, 'DEF Inc') print(building.get_floor_data(2))
Можно использовать магический метод __getitem__
и его аналог __setitem__
, чтобы сделать использование класса Building()
более приятным.
class Building(object): def __init__(self, floors): self._floors = [None]*floors def __setitem__(self, floor_number, data): self._floors[floor_number] = data def __getitem__(self, floor_number): return self._floors[floor_number] # Строим здание с 4 этажами building = Building(4) building[0] = 'Reception' building[1] = 'ABC Corp' building[2] = 'DEF Inc' print(building[2])
Используя в примере специальные методы __getitem__
и __setitem__
таким образом, мы решили рассматривать здание как контейнер этажей. Также можно реализовать итератор для класса Building()
и, возможно, даже взятие среза - то есть получать данные более чем с одного этажа за раз - это зависит от того, какие действия требуются от реализации конкретного класса.