Почему определение __getitem__ в классе делает его итеративным в python?

Почему определение __getitem__ в классе делает его итеративным?

Например, если я напишу:

class b:
  def __getitem__(self, k):
    return k

cb = b()

for k in cb:
  print k

Я получаю вывод:

0
1
2
3
4
5
6
7
8
...

Я действительно ожидал бы увидеть ошибку, возвращаемую из "для k в cb:"

6 ответов

Решение

Если вы посмотрите на PEP234, определяющий итераторы, он говорит:

1. An object can be iterated over with "for" if it implements
   __iter__() or __getitem__().

2. An object can function as an iterator if it implements next().

Поддержка итерации для __getitem__ может рассматриваться как "унаследованная особенность", которая позволила более плавный переход, когда PEP234 представил итеративность в качестве основной концепции. Это относится только к классам без __iter__ чья __getitem__ принимает целые числа 0, 1 и & и повышает IndexError как только индекс становится слишком высоким (если вообще когда-либо), как правило, классы "sequence" кодируются ранее __iter__ появился (хотя ничто не мешает вам кодировать новые классы таким же образом).

Лично я бы предпочел не полагаться на это в новом коде, хотя это не считается устаревшим и не исчезает (прекрасно работает и в Python 3), так что это просто вопрос стиля и вкуса ("явное лучше, чем неявное", поэтому Я бы предпочел явно поддерживать итеративность, а не полагаться на __getitem__ поддерживая это безоговорочно для меня - но, не большое).

__getitem__ предшествовал протоколу итератора и в прошлом был единственным способом сделать вещи итеративными. Как таковой, он все еще поддерживается как метод итерации. По сути, протокол для итерации:

  1. Проверьте на __iter__ метод. Если он существует, используйте новый протокол итерации.

  2. В противном случае попробуйте позвонить __getitem__ с последовательно большими целочисленными значениями, пока он не вызовет IndexError.

(2) раньше был единственный способ сделать это, но имел недостаток в том, что он предполагал больше, чем нужно для поддержки только итерации. Чтобы поддерживать итерацию, вы должны были поддерживать произвольный доступ, который был намного дороже для таких вещей, как файлы или сетевые потоки, где переход вперед был легким, но переход назад потребовал бы хранения всего. __iter__ допускается итерация без произвольного доступа, но так как произвольный доступ обычно допускает итерацию в любом случае, и потому что нарушение обратной совместимости будет плохим, __getitem__ все еще поддерживается.

Специальные методы, такие как __getitem__ добавить специальные поведения для объектов, в том числе итерации.

http://docs.python.org/reference/datamodel.html GetItem

msgstr "для циклов ожидаем, что IndexError будет вызван для недопустимых индексов, чтобы обеспечить правильное определение конца последовательности."

Поднимите IndexError, чтобы указать конец последовательности.

Ваш код в основном эквивалентен:

i = 0
while True:
    try:
        yield object[i]
        i += 1
    except IndexError:
        break

Где объект - это то, что вы перебираете в цикле for.

Это так по историческим причинам. До Python 2.2 __getitem__ был единственным способом создания класса, который можно было бы перебрать с помощью цикла for. В 2.2 был добавлен протокол __iter__, но для обеспечения обратной совместимости __getitem__ все еще работает для циклов.

Так как cb[0] такой же как cb.__getitem__(0), Смотрите документацию по Python по этому вопросу.

Другие вопросы по тегам