Почему итераторы в Python вызывают исключение?

48

Вот синтаксис для итераторов в Java (несколько похожий синтаксис в C #):

Iterator it = sequence.iterator();

while (it.hasNext()) {
    System.out.println(it.next());
}

Что имеет смысл. Вот эквивалентный синтаксис в Python:

it = iter(sequence)
while True:
    try:
        value = it.next() 
    except StopIteration:
        break
    print(value)

Я думал, что исключения должны были использоваться только в исключительных обстоятельствах.

Почему Python использует исключения для остановки итерации?

NullUserException
источник

Ответы:

50

Есть очень Pythonic способ написать это выражение без явной записи блока try-Кроме StopIteration:

# some_iterable is some collection that can be iterated over
# e.g., a list, sequence, dict, set, itertools.combination(...)

for value in some_iterable:
    print(value)

Вы можете прочитать соответствующие PEP 234 255, если хотите узнать больше о том, почему он StopIterationбыл представлен, и о логике итераторов.

Общий принцип в python - иметь один способ что-то сделать (см. import this), И, предпочтительно, его красивый, явный, читаемый и простой, который удовлетворяет метод pythonic. Ваш эквивалентный код необходим только потому, что python не предоставляет итераторам функцию- hasNextчлен; предпочитая, чтобы люди просто перебирали итераторы напрямую (и, если вам нужно сделать что-то еще, просто попробуйте прочитать это и поймать исключение).

Этот автоматический перехват StopIterationисключения в конце итератора имеет смысл и является аналогом EOFErrorповышенного значения, если вы читаете за концом файла.

доктор джимбоб
источник
6
Конечно, способ "Pythonic" выглядит скорее как "for value in sequence:", а не "for value in iter (sequence):" .. Update post?
Ям Маркович
15
@Yam: я согласен. Не стоит брать существующую последовательность и преобразовывать ее в итератор просто для того, чтобы применить к ней цикл for; последовательность уже итеративна, поэтому преобразование a listв a listiteratorбессмысленно. Я сохранил первую строчку только следовать за отправную точкой NullUserException, в объяснить , как вы должны перебирает итератор, который является таким же образом , вы должны перебирает любую итерацию ( list, set, str, tuple, dict, file, generatorи т.д.). Я мог бы сделать что-то вроде it = itertools.combinations("ABCDE", 2)лучшего примера значимого итератора.
Доктор Джимбоб
1
it = iter(sequence)не нужен
Caridorc
2
@Caridorc - Если вы читаете комментарии, вы бы ответили на свой вопрос. Это не нужно и было сделано для того, чтобы следовать отправной точке вопроса (где они явно задавали вопросы iterators), и вам нужно iterявно сгенерировать iterator(try type([])( list) vs type(iter([]))( listiterator)).
Доктор Джимбоб
//, @drjimbob, вы подняли отличную точку во втором комментарии к этому вопросу. Я немного новичок в расширенных возможностях итераций, и я бы не понял этого, если бы не читал комментарии. Я думаю, что было бы полезно многим из нас, бедных самоучек, если бы мы увидели этот момент о том, как сам вопрос может быть изменен на «Путь Питона», как первую, основную часть ответа.
Натан Басанезе
28

Причина, по которой python использует исключение для остановки итерации, задокументирована в PEP 234 :

Был задан вопрос, не слишком ли дорого исключение, сигнализирующее об окончании итерации. Было предложено несколько альтернатив для исключения StopItered: специальное значение End для обозначения конца, функция end () для проверки завершения итератора, даже повторное использование исключения IndexError.

  • У специального значения есть проблема в том, что если последовательность когда-либо содержит это специальное значение, цикл над этой последовательностью преждевременно завершится без какого-либо предупреждения. Если опыт работы со строками C с нулевым символом в конце не научил нас тем проблемам, которые это может вызвать, представьте себе проблему, которую инструмент интроспекции Python мог бы перебирать по списку всех встроенных имен, предполагая, что специальное значение End было встроенным по названию!

  • Вызов функции end () потребует двух вызовов за итерацию. Два звонка намного дороже, чем один звонок плюс тест на исключение. Особенно критичным по времени для цикла может быть очень дешево для исключения.

  • Повторное использование IndexError может привести к путанице, поскольку это может быть подлинной ошибкой, которая может быть замаскирована преждевременным завершением цикла.

Примечание: идиоматический способ прохождения последовательности по Python выглядит так:

for value in sequence:
    print (value)
lesmana
источник
20

Это разница в философии. Философия дизайна Pythonic - это EAFP :

Проще просить прощения, чем разрешения. Этот общий стиль кодирования Python предполагает наличие допустимых ключей или атрибутов и перехватывает исключения, если предположение оказывается ложным. Это чистый и быстрый стиль характеризуется наличием многих tryи exceptзаявлений. Техника контрастирует со стилем LBYL, общим для многих других языков, таких как C ...

Чарльз Э. Грант
источник
7

Просто у реализации Java есть hasNext()метод, так что вы можете проверить наличие пустого итератора перед выполнением next(). Когда вы делаете вызов next()на итераторе Java без каких - либо элементов , Влево, NoSuchElementExceptionотбрасываются .

Таким образом, вы можете сделать try..catch в Java, как try..except в Python. И да, согласно предыдущему ответу, философия очень важна в мире Pythonic.

Яти Сагаде
источник