Почему python использует 'else' после циклов for и while?
Я понимаю, как работает эта конструкция:
for i in range(10):
print(i)
if i == 9:
print("Too big - I'm giving up!")
break
else:
print("Completed successfully")
Но я не понимаю, почему здесь в качестве ключевого слова используется else
, поскольку это предполагает, что рассматриваемый код выполняется только в том случае, если for
блок не завершается, что противоположно тому, что он делает! Независимо от того, как я думаю об этом, мой мозг не может плавно перейти от for
инструкции к else
блоку. Для меня continue
or continuewith
имело бы больше смысла (и я пытаюсь приучить себя читать его как таковой).
Мне интересно, как программисты Python читают эту конструкцию в своей голове (или вслух, если хотите). Возможно, я упускаю что-то, что сделало бы такие блоки кода более легко расшифруемыми?
Этот вопрос касается лежащего в основе проектного решения, т.Е. Почему полезно иметь возможность писать этот код. Смотрите также предложение Else в операторе Python while для конкретного вопроса о том, что означает синтаксис.
Переведено автоматически
Ответ 1
Обычная конструкция заключается в выполнении цикла до тех пор, пока что-то не будет найдено, а затем для выхода из цикла. Проблема в том, что если я вырываюсь из цикла или цикл заканчивается, мне нужно определить, какой случай произошел. Один из методов - создать флаг или сохранить переменную, которая позволит мне выполнить второй тест, чтобы увидеть, как был завершен цикл.
Например, предположим, что мне нужно выполнить поиск по списку и обрабатывать каждый элемент, пока не будет найден элемент с флагом, а затем остановить обработку. Если элемент с флагом отсутствует, то необходимо вызвать исключение.
Используя конструкцию Python for
...else
, у вас есть
for i in mylist:
if i == theflag:
break
process(i)
else:
raise ValueError("List argument missing terminal flag.")
Сравните это с методом, который не использует этот синтаксический сахар:
flagfound = False
for i in mylist:
if i == theflag:
flagfound = True
break
process(i)
if not flagfound:
raise ValueError("List argument missing terminal flag.")
В первом случае raise
тесно связан с циклом for, с которым он работает. Во втором привязка не такая сильная, и при обслуживании могут возникать ошибки.
Ответ 2
Это странная конструкция даже для опытных программистов на Python. При использовании в сочетании с циклами for это в основном означает "найдите некоторый элемент в iterable, else, если ничего не найдено, сделайте ...". Как в:
found_obj = None
for obj in objects:
if obj.key == search_key:
found_obj = obj
break
else:
print('No object found.')
Но всякий раз, когда вы видите эту конструкцию, лучшей альтернативой является либо инкапсуляция поиска в функцию:
def find_obj(search_key):
for obj in objects:
if obj.key == search_key:
return obj
Или используйте понимание списка:
matching_objs = [o for o in objects if o.key == search_key]
if matching_objs:
print('Found {}'.format(matching_objs[0]))
else:
print('No object found.')
Семантически он не эквивалентен двум другим версиям, но достаточно хорошо работает в коде, критически важном для производительности, где не имеет значения, повторяете ли вы весь список или нет. Другие могут не согласиться, но лично я бы никогда не стал использовать блоки for-else или while-else в производственном коде.
Смотрите также [Python-идеи] Краткое описание потоков for ... else
Ответ 3
Есть отличная презентация Раймонда Хеттингера, озаглавленная " Преобразование кода в красивый идиоматический Python", в которой он кратко описывает историю for ... else
конструкции. Соответствующий раздел "Различение нескольких точек выхода в циклах" начинается в 15: 50 и продолжается около трех минут. Вот основные моменты.:
for ... else
Конструкция была разработана Дональдом Кнутом в качестве замены для определенныхGOTO
вариантов использования;- Повторное использование
else
ключевого слова имело смысл, потому что "это то, что использовал Кнут, и люди знали, что в то время во все [for
инструкции] были встроеныif
иGOTO
под ними, и они ожидалиelse
;" - Оглядываясь назад, это должно было называться "no break" (или, возможно, "nobreak"), и тогда это не сбивало бы с толку.*
Итак, если возникает вопрос: "Почему они не меняют это ключевое слово?" тогда Cat Plus Plus, вероятно, дал наиболее точный ответ – на данном этапе это было бы слишком разрушительно для существующего кода, чтобы быть практичным. Но если вопрос, который вы действительно задаете, заключается в том, почему else
изначально использовался повторно, что ж, очевидно, в то время это казалось хорошей идеей.
Лично мне нравится компромисс в виде комментирования # no break
в строке везде, где else
с первого взгляда может быть ошибочно принято за принадлежность к циклу. Это достаточно ясно и лаконично. Этот параметр кратко упоминается в резюме, на которое ссылался Бьорн в конце своего ответа:
Для полноты картины я должен упомянуть, что с небольшим изменением синтаксиса программисты, которым нужен этот синтаксис, могут получить его прямо сейчас:
for item in sequence:
process(item)
else: # no break
suite
* Бонусная цитата из этой части видео: "Точно так же, как если бы мы вызвали lambda makefunction, никто не спросил бы:"Что делает lambda?"
Ответ 4
Чтобы упростить, вы можете представить это следующим образом;
- Если он встретит
break
команду вfor
цикле,else
часть не будет вызвана. - Если он не встретит
break
команду вfor
цикле, будет вызванаelse
часть.
Другими словами, если итерация цикла for не "прерывается" с помощью break
, будет вызвана else
часть.