Почему определение __getitem__ в классе делает его итеративным в python? - PullRequest
57 голосов
/ 29 мая 2009

Почему определение __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
...

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

Ответы [ 6 ]

60 голосов
/ 29 мая 2009

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

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

46 голосов
/ 29 мая 2009

Если вы посмотрите на 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().
25 голосов
/ 29 мая 2009

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

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

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

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

6 голосов
/ 29 мая 2009

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

http://docs.python.org/reference/datamodel.html#object.getitem

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

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

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

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

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

5 голосов
/ 29 мая 2009

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

2 голосов
/ 29 мая 2009

Потому что cb[0] совпадает с cb.__getitem__(0). См. документацию по питону по этому вопросу.

...