Чому python використовує "else" після циклів "for" та "while"?


480

Я розумію, як працює ця конструкція:

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або continuewithбуло б більше сенсу (і я намагаюся навчити себе читати це як таке).

Мені цікаво, як кодери Python читають цю конструкцію в голові (або вголос, якщо вам подобається). Можливо, мені не вистачає чогось, що зробило б такі кодові блоки легшими для розшифровки?


26
Можливо, ви хочете перевести це в голові "тоді".
Марцін

63
Не забувайте про ключовий рядок дзен Python: "... цей спосіб може бути спочатку не очевидним, якщо ви не голландці".
Даніель Роузмен

50
У голові я перекладаю це як "якщо не зламати" . І, оскільки breakвикористовується багато в циклі "я знайшов це" , ви можете перевести це на "якщо не знайдено" , що недалеко від elseпрочитаного
MestreLion

29
Я думаю, що у багатьох людей тут справжнє питання: "Яка різниця між for ... else foo()та просто введенням foo()циклу for?" І відповідь полягає в тому, що вони поводяться по-різному, лише якщо цикл містить break(як детально описано нижче).
Сем Кауффман

10
Точка з комою в пітоні ... болять мої очі .., хоча це синтаксично правильно, це не дуже добре
DarkCygnus

Відповіді:


278

Це дивна конструкція навіть для досвідчених кодерів Python. При використанні спільно з for-петлями, це в основному означає "знайти якийсь елемент в ітерабелі, інакше, якщо жодного не було знайдено зробити ...". Як і в:

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-ідеї] Підсумок для ... інші теми


50
Ознайомлення зі списком - це неправильний однорівень. Якщо ви шукаєте один елемент, як у forприкладах циклу, і хочете використовувати розуміння виразу / списку генератора, то ви хочете next((o for o in objects if o.key == search_key), None)або оберніть його в try/ exceptі не використовуйте значення за замовчуванням замість if/ else.
agf

4
і як відповідь Ланс Хельстен, є фактичні випадки, коли краще використовувати for/elseконструкцію.
andrean

5
Ура. У мене був сильно розрізаний файл, в якому elseпопався в пару, forі я не мав уявлення, що це легально.
maxywb

3
Я думаю, що цикл for є найбільш очевидним з конструктів там.
Майлз Рут

14
Варто згадати, що інше запуск буде запускатися, навіть якщо цикл for має значення, якщо breakвиразник не буде експлікацією, як у цьому прикладі. З наведених вище документів: "У elseпропозиції є ще одна сприйнята проблема: якщо її немає breakв циклі, elseпункт функціонально є зайвим." наприкладfor x in [1, 2, 3]:\n print x\n else:\n print 'this executes due to no break'
dhackner

586

Поширена конструкція - запустити цикл, поки щось не буде знайдено, а потім вирватися з циклу. Проблема полягає в тому, що якщо я вирваюся з циклу або закінчується цикл, мені потрібно визначити, який випадок стався. Один із методів - створити прапор або зберегти змінну, яка дозволить мені зробити другий тест, щоб побачити, як цикл вийшов.

Наприклад, припустимо, що мені потрібно шукати список і обробляти кожен елемент, поки не буде знайдено елемент прапора, а потім припинити обробку. Якщо елемент прапора відсутній, потрібно вилучити виняток.

Використовуючи конструкцію 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, з яким він працює. По-друге, прив'язка не настільки сильна, і під час технічного обслуговування можуть вводитися помилки.


69
Це пояснює це краще, ніж обрана відповідь, коли автор насправді не отримує того, про що йдеться!
erikbwork

17
Я б сказав, що цей синтаксичний цукор може зіпсувати зуби вашого проекту. Це не зробило б Python: the good partsкниги.
boatcoder

1
Чи можете ви підтвердити, що у вашому прикладі process(i)відбувається з кожним предметом mylistстрого раніше theflag, а не theflagсамою собою? Це те, що було призначено?
блі

4
processбуде виконано на кожному, iщо існує в списку до того, як theflagбуде досягнуто, воно не буде виконуватися на елементах у списку після theflag, і воно не буде виконуватися далі theflag.
Ленс Хельстен

1
інше твердження також виконується, якщо в ітерабельному файлі немає елементів
Lost Crotchet

173

Є відмінна презентація Реймонда Хеттінгера під назвою « Трансформація коду в прекрасний ідіоматичний пітон» , в якій він коротко розглядає історію for ... elseконструкції. Відповідний розділ - "Розрізнення декількох точок виходу в петлях", починаючи з 15:50 і триває близько трьох хвилин. Ось основні моменти:

  • for ... elseКонструкція була розроблена Дональдом Кнутом в якості заміни для деяких GOTOвипадків застосування;
  • Повторне використання elseключового слова мало сенс, тому що "це те, що використовував Кнут, і люди знали, що в той час усі [ forзаяви] вбудовувались ifі GOTOпід них, і вони очікували, що else"
  • Заднім числом його слід було б назвати "без розриву" (або, можливо, "відключення"), і тоді це не було б заплутаним. *

Отже, якщо питання: "Чому вони не змінюють це ключове слово?" тоді Cat Plus Plus, ймовірно, дав найбільш точну відповідь - на даний момент це було б занадто руйнівним для існуючого коду, щоб бути практичним. Але якщо питання, яке ви насправді ставите, - чому elseйого в першу чергу повторно використовували, ну, мабуть, це тоді здавалося гарною ідеєю.

Особисто мені подобається компроміс коментувати # no breakв Інтернеті, де б elseможна було помилитися, на перший погляд, як належність всередині циклу. Це досить чітко і стисло. Цей варіант отримує коротку згадку у резюме, яке Бьорн посилав наприкінці своєї відповіді:

Для повноти слід зазначити, що при незначній зміні синтаксису програмісти, які хочуть цього синтаксису, можуть мати його прямо зараз:

for item in sequence:
    process(item)
else:  # no break
    suite

* Цитата бонусу з тієї частини відео: "Як і коли б ми назвали функцію лямбда , ніхто не запитав:" Що робить лямбда? "


33

Тому що вони не хотіли вводити нове ключове слово до мови. Кожен з них викрадає ідентифікатор і викликає зворотні проблеми сумісності, тому зазвичай це в крайньому випадку.


2
Здається, finallyу цьому випадку був би кращий вибір. Чи останнє ключове слово ще не було в момент введення цієї конструкції?
Ponkadoodle

26
@Wallacoloo finallyне набагато кращий, тому що це означає, що блок завжди буде виконуватися після циклу, і це не так (адже це було б зайвим, просто поставивши код для запуску після циклу).
Cat Plus Plus

Це також не може бути, finallyтому що інше застереження виконується і тоді, коли continueвоно використовується в циклі for - це, можливо, багато разів і не тільки в кінці.
pepr

6
Виконання elseпункту @pepr не впливає continue( документи та тестовий код )
Air

@AirThomas: +1. Ти правий. Виконання elseвиконується лише тоді, коли continueбуло останньою ітерацією.
пепр

33

Щоб зробити це просто, можна про це думати;

  • Якщо він зіткнеться з breakкомандою в forциклі, elseчастина не буде викликана.
  • Якщо вона не стикається з breakкомандою в forциклі, elseчастина буде викликана.

Іншими словами, якщо ітерація циклу не "зламана" break, elseчастина буде викликана.


elseБлок також не виконано , якщо тіло циклу викликає виняток.
Амаль К

17

Найпростіший спосіб, який я знайшов, щоб "отримати" те, що зробив для / else, і, що ще важливіше, коли його використовувати, - сконцентруватися на тому, куди переходить заява перерви. Конструкція For / else - це єдиний блок. Перерва вискакує з блоку, і так перескакує "інше". Якщо зміст іншого пункту просто слідує за застереженням, воно ніколи не буде переходити, і тому еквівалентну логіку потрібно було б забезпечити, поставивши його в if. Це було сказано раніше, але не зовсім в цих словах, тому це може допомогти комусь іншому. Спробуйте запустити наступний фрагмент коду. Я від усієї душі виступаю за коментар "без перерви" для ясності.

for a in range(3):
    print(a)
    if a==4: # change value to force break or not
        break
else: #no break  +10 for whoever thought of this decoration
    print('for completed OK')

print('statement after for loop')

"Перерва вискакує з блоку, і тому перескакує" над "іншим пунктом" - хоча це може бути корисним як спосіб "отримати" for:/ else:, це насправді не дає виправдання для ключового слова else. З огляду на обрамлення, подане тут, then:схоже, це було б набагато природніше. (Там є причини elseбути обрані, наведені в інших відповідях - вони просто не передбачені тут.)
Марк Емері

16

Я думаю, що в документації є чудове пояснення іншого , продовжуйте

[...] він виконується, коли цикл закінчується через вичерпання списку (з for) або коли умова стає хибною (з тим часом), але не тоді, коли цикл припиняється за допомогою заяви перерви ".

Джерело: Документи Python 2: Підручник з потоку управління


13

Я читав це щось на кшталт:

Якщо все ж за умовами запустити цикл, зробіть речі, ще щось зробіть.


Ви все ще на умовах корисні (+1), хоча це неправильно - це людина ;-)
Вовк

-1; ця вимова for:/ else:робить це звуком, як else:воля завжди працюватиме після циклу, що не так.
Марк Амері

11

Оскільки технічна частина отримала значну відповідь, мій коментар якраз пов’язаний із плутаниною, яка створює це перероблене ключове слово.

Будучи Python дуже красномовною мовою програмування, неправильне використання ключового слова є більш відомим. elseКлючове слово прекрасно описує частину потоку дерева рішень, «якщо ви не можете зробити це, (ще) зробити це». Це мається на увазі на нашою власною мовою.

Натомість використання цього ключового слова із whileта forоператорами створює плутанину. Причина, наша кар’єра програмістів навчила нас, що elseзаява знаходиться в дереві рішень; її логічна сфера , обгортка, яка умовно повертає шлях для слідування. Тим часом висловлювання циклу мають образно виражену мету досягти чогось. Мета досягається після безперервних ітерацій процесу.

if / else вкажіть шлях, який слід пройти . Петлі дотримуються контуру до тих пір, поки «мета» не буде виконана .

Проблема полягає в тому, що elseце слово, яке чітко визначає останній варіант в умові. У семантику цього слова обидва поділяють на Python і людської мови. Але інше слово в людській мові ніколи не використовується для позначення дій, які хтось чи щось вчинить після того, як щось буде виконано. Він буде використовуватися, якщо в процесі його завершення виникає проблема (більше схожа на заяву про перерву ).

Зрештою, ключове слово залишиться в Python. Зрозуміло, що це була помилка, зрозуміліше, коли кожен програміст намагається придумати історію, щоб зрозуміти її використання, як якийсь мнемонічний пристрій. Мені б хотілося, якби вони обрали замість нього ключове слово then. Я вважаю, що це ключове слово ідеально вписується в той ітеративний потік, окупність після циклу.

Це нагадує таку ситуацію, яка виникає у якоїсь дитини після виконання кожного кроку зі збирання іграшки: А ЦЕ що, тато?


Я думаю, що ця відповідь стосується питання плутанини, я думаю, що про це говорив ОП. Ключове слово else робить зовсім протилежне тому, що ви очікували б від англійського значення else, якщо приєднатися до дії для. Теоретично, для ... ще могло попрацювати по-іншому, щоб ви опинилися в іншій частині, коли цикл вирваний, але проблема полягає в тому, щоб використовувати його для пошуку елемента x і обробляти випадок, коли x є не знайдено, можливо, вам доведеться використовувати прапор або інший тест після цілого для .. else construct
Spacen Jasset

7

Я читаю це на кшталт "Коли iterableповністю вичерпано, і виконання збирається перейти до наступного твердження після закінчення for, буде виконано інше." Таким чином, коли ітерація буде порушена break, це не буде виконано.


6

Я погоджуюся, це більше схоже на "elif not [умова (и) перерва на підвищення"] ".

Я знаю, що це стара тема, але я зараз переглядаю те саме питання, і я не впевнений, що хтось схопив відповідь на це питання так, як я це розумію.

Для мене існують три способи "читання" elseу For... elseабо While... elseтвердженнях, всі вони рівнозначні:

  1. else == if the loop completes normally (without a break or error)
  2. else == if the loop does not encounter a break
  3. else == else not (condition raising break) (імовірно, існує така умова, або у вас не було б циклу)

Отже, по суті, "else" в циклі - це справді "elif ...", де "..." є (1) немає перерви, що еквівалентно (2) NOT [умова (s) підвищення перерви].

Я думаю, що ключовим є те, що elseбезглуздий без "перерви", тому for...elseвключає:

for:
    do stuff
    conditional break # implied by else
else not break:
    do more stuff

Отже, істотними елементами for...elseциклу є наступні, і ви їх читали більш простою англійською мовою як:

for:
    do stuff
    condition:
        break
else: # read as "else not break" or "else not condition"
    do more stuff

Як говорили інші плакати, зазвичай робиться перерва, коли ви зможете знайти те, що шукає ваш цикл, тому else: стає "що робити, якщо цільовий елемент не знаходиться".

Приклад

Ви також можете використовувати обробку винятків, перерви та петлі разом.

for x in range(0,3):
    print("x: {}".format(x))
    if x == 2:
        try:
            raise AssertionError("ASSERTION ERROR: x is {}".format(x))
        except:
            print(AssertionError("ASSERTION ERROR: x is {}".format(x)))
            break
else:
    print("X loop complete without error")

Результат

x: 0
x: 1
x: 2
ASSERTION ERROR: x is 2
----------
# loop not completed (hit break), so else didn't run

Приклад

Простий приклад, коли перерва потрапляє.

for y in range(0,3):
    print("y: {}".format(y))
    if y == 2: # will be executed
        print("BREAK: y is {}\n----------".format(y))
        break
else: # not executed because break is hit
    print("y_loop completed without break----------\n")

Результат

y: 0
y: 1
y: 2
BREAK: y is 2
----------
# loop not completed (hit break), so else didn't run

Приклад

Простий приклад, коли немає перерви, немає умови, що підвищує перерву, і не виникає помилок.

for z in range(0,3):
     print("z: {}".format(z))
     if z == 4: # will not be executed
         print("BREAK: z is {}\n".format(y))
         break
     if z == 4: # will not be executed
         raise AssertionError("ASSERTION ERROR: x is {}".format(x))
else:
     print("z_loop complete without break or error\n----------\n")

Результат

z: 0
z: 1
z: 2
z_loop complete without break or error
----------

6

elseКлючове слово може збивати з пантелику тут, і , як багато людей відзначали, що - щось на зразок nobreak, notbreakбільш доречно.

Для того, щоб зрозуміти for ... else ...логічно, порівняйте його з try...except...elseні if...else..., більшість програмістів python знайомі з таким кодом:

try:
    do_something()
except:
    print("Error happened.") # The try block threw an exception
else:
    print("Everything is find.") # The try block does things just find.

Аналогічно, подумайте breakяк про особливий вид Exception:

for x in iterable:
    do_something(x)
except break:
    pass # Implied by Python's loop semantics
else:
    print('no break encountered')  # No break statement was encountered

Різниця має на pythonувазі, except breakі ви не можете її виписати, тому вона стає:

for x in iterable:
    do_something(x)
else:
    print('no break encountered')  # No break statement was encountered

Так, я знаю, що це порівняння може бути важким і стомлюючим, але все-таки з’ясовує плутанину.


Ви повинні створити посилання на ресурс, коли ви копіюєте з нього: замітки Пітона Ніка Коглана .
Godaygo

@godaygo дякую за посилання. Я читав і приймав цю концепцію, коли вперше вивчав пітон, не запам’ятовував джерело при написанні відповіді.
cizixs

@cizixs Ви "не запам’ятали джерело", але просто трапилось включити цілі речення коментарів, ідентичні оригіналу? Ooookaaaay.
Марк Амері

5

Коди в elseблоці операторів будуть виконуватися, коли forцикл не був розбитий.

for x in xrange(1,5):
    if x == 5:
        print 'find 5'
        break
else:
    print 'can not find 5!'
#can not find 5!

З Документів: перервіть і продовжуйте Заяви та інше Клаузи щодо циклів

Висловлювання циклу можуть мати інше застереження; він виконується, коли цикл закінчується через вичерпання списку (з for) або коли умова стає хибною (з тим часом), але не тоді, коли цикл припиняється за допомогою заяви перерви. Про це свідчить такий цикл, який шукає прості числа:

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             break
...     else:
...         # loop fell through without finding a factor
...         print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

(Так, це правильний код. Подивіться уважно: інше застереження належить циклу for, а не оператору if.)

При використанні з циклом, клавіша else має більше спільного з пунктом else оператора спробу, ніж у випадку, якщо твердження: оператори if оператора try: запускається, коли не виникає виняток, а пункт "цикл" циклу працює, коли не відбувається перерва. . Докладніше про спробу заявки та винятки див. У розділі Обробка виключень.

Виписка «продовження», також запозичена з C, продовжується наступною ітерацією циклу:

>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print("Found an even number", num)
...         continue
...     print("Found a number", num)
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9

1
Це нічого не додає і не відповідає на питання, що не як, а чому .
Повітря

5

Ось спосіб подумати про те, що я ще не бачив, щоб хтось згадував вище:

По-перше, пам’ятайте, що for-петлі - це в основному синтаксичний цукор навколо циклів. Наприклад, петля

for item in sequence:
    do_something(item)

можна переписати (приблизно) як

item = None
while sequence.hasnext():
    item = sequence.next()
    do_something(item)

По-друге, пам’ятайте, що while-петлі в основному просто повторюються if-блоки! Ви завжди можете прочитати цикл "час", як "якщо ця умова справжня, виконайте тіло, а потім поверніться і ще раз перевірте".

Отже, поки / else має ідеальний сенс: це точно така ж структура, як ніби / ще, з доданою функціональністю циклічного циклу, поки умова не стане помилковою, а не просто перевірити умову один раз.

І тоді для / else має ідеальний сенс також: оскільки всі for-цикли - це лише синтаксичний цукор поверх циклів while, то вам просто потрібно розібратися, що таке основний неявний умовний цикл while-loop, а тоді інше відповідає, коли це стан стає помилковим.


4

Чудові відповіді:

  • це, що пояснює історію, і
  • це дає право цитувати полегшення вашого перекладу / розуміння.

Моя примітка тут випливає з того, що одного разу Дональд Кнут сказав (вибачте, не можу знайти посилання), що існує конструкція, де поки-ще не відрізняється від if-else, а саме (в Python):

x = 2
while x > 3:
    print("foo")
    break
else:
    print("boo")

має такий же потік (за винятком низьких перепадів рівня), як:

x = 2
if x > 3:
    print("foo")
else:
    print("boo")

Річ у тім, що if-else може розглядатися як синтаксичний цукор для while-else, який мається breakна увазі в кінці ifблоку. Протилежне значення, до якого whileцикл поширюється if, є більш поширеним (це лише повторне / петельне умовне перевірвання), оскільки ifвоно часто викладається раніше while. Однак це неправда, тому що це означатиме, що elseблок в, а інше буде виконуватися кожен раз коли умова не відповідає дійсності.

Щоб полегшити ваше розуміння, думайте про це так:

Без breakі returnт.д. цикл закінчується лише тоді, коли умова більше не відповідає дійсності, і в такому випадку elseблок також виконується один раз. У випадку Python forви повинні розглянути forпетлі у стилі C (з умовами) або перекласти їх у while.

Ще одна примітка:

Передчасний breakі returnт.д. в циклі унеможливлює умову, що стає хибною, оскільки виконання вискочило з циклу, коли умова була справжньою, і вона ніколи не повернеться, щоб перевірити її знову.


3

Ви можете подумати про це так, elseяк і в решті речей, або інших речей, що не було зроблено в циклі.


3
for i in range(3):
    print(i)

    if i == 2:
        print("Too big - I'm giving up!")
        break;
else:
    print("Completed successfully")

"інакше" тут божевільно просто, просто означає

1, "якщо for clauseзавершено"

for i in range(3):
    print(i)

    if i == 2:
        print("Too big - I'm giving up!")
        break;
if "for clause is completed":
    print("Completed successfully")

Писати такі довгі висловлювання, як "закінчення пункту завершено", це означає, що вони вводять "ще".

else ось якщо за своєю природою.

2, однак як щодо for clause is not run at all

In [331]: for i in range(0):
     ...:     print(i)
     ...: 
     ...:     if i == 9:
     ...:         print("Too big - I'm giving up!")
     ...:         break
     ...: else:
     ...:     print("Completed successfully")
     ...:     
Completed successfully

Тож цілком твердження - це логічне поєднання:

if "for clause is completed" or "not run at all":
     do else stuff

або кажучи так:

if "for clause is not partially run":
    do else stuff

або таким чином:

if "for clause not encounter a break":
    do else stuff

інше виступає як "транзакція" в SQL.
Обчислення

2

Ось ще один ідіоматичний випадок використання, окрім пошуку. Скажімо, ви хотіли зачекати, коли умова справдиться, наприклад, порт буде відкритий на віддаленому сервері, а також деякий час очікування. Тоді ви можете використовувати таку while...elseконструкцію:

import socket
import time

sock = socket.socket()
timeout = time.time() + 15
while time.time() < timeout:
    if sock.connect_ex(('127.0.0.1', 80)) is 0:
        print('Port is open now!')
        break
    print('Still waiting...')
else:
    raise TimeoutError()

1

Я просто намагався знову зрозуміти це. Я виявив, що допомагає наступне!

• Подумайте, elseяк про пару з ifвнутрішньою петлею (замість з for), - якщо умова виконана, тоді перервіть цикл, інакше зробіть це - за винятком того, що він elseпоєднаний з декількома ifs!
• Якщо жодних людей взагалі ifне влаштовували, то робіть це else.
• Кілька ifs також можна вважати if- elifs!


-2

Я розглядаю структуру як для (якщо) A else B, а для (if) -else - це спеціальний if-else , приблизно . Це може допомогти зрозуміти інше .

A і B виконується щонайбільше одразу, що таке саме, як структура if-else.

for (if) може розглядатися як спеціальний if, який робить цикл, щоб спробувати виконати умову if. Як тільки умова if буде виконано, A і перерва ; Ще , Б.


-2

Python використовує цикл else after for і while, так що якщо до циклу нічого не стосується, трапляється щось інше. Наприклад:

test = 3
while test == 4:
     print("Hello")
else:
     print("Hi")

Вихід буде "Привіт" знову і знову (якщо я прав).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.