Як я можу зрозуміти пункт `else` циклів Python?


191

Багато програмістів Python, напевно, не знають, що синтаксис whileциклів і forциклів включає необов'язковий else:пункт:

for val in iterable:
    do_something(val)
else:
    clean_up()

Тіло elseпропозиції є хорошим місцем для певних видів очищення, і виконується при нормальному завершенні циклу: Тобто, виходить із циклу returnабо breakпропускає elseпункт; вихід після його continueвиконання. Я знаю , що це тільки тому , що я тільки що подивився його (ще раз), тому що я ніколи не можу пригадати , колиelse пропозиція виконується.

Завжди? Про "провал" циклу, як випливає з назви? Про регулярне припинення? Навіть якщо цикл закривається return? Я ніколи не можу бути повністю впевненим, не дивлячись на це.

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

Я впевнений, що про це було досить багато дискусій, і я можу уявити, що вибір був зроблений на узгодження з пунктом tryзаяви else:(який я також повинен шукати), і з метою не додавати до списку Застережені слова Python. Можливо, причини вибору elseз’ясують його функцію та зроблять її більш запам'ятовуваною, але я після того, як підключувати ім’я до функції, а не після історичного пояснення як такого.

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


23
Як щодо "якщо щось залишилося для повторення ... інше"
OneCricketeer

4
Я думаю, ви можете згадати це зараз, коли написали це запитання :)
Джаспер

11
що elseкошти в основному, «якщо умова продовження не може ». У традиційній для циклу умовою продовження зазвичай є i < 42, у цьому випадку цю частину ви можете розглядати якif i < 42; execute the loop body; else; do that other thing
njzk2

1
Це все правда, і мені особливо подобається відповідь drawoc, але інша річ, яку слід врахувати, полягає в тому, що ще є доступне ключове слово, яке також має синтаксичний сенс. Ви можете знати спробу / за винятком і, можливо, спробувати / за винятком / нарешті, але він також має інше - запустіть цей код, якщо не сталося винятку. Що стосується btw, це не те саме, що переміщення цього коду під застережним пунктом - обробку винятків найкраще використовувати, коли вузько націлені. Отже, хоча це має концептуальний сенс - відповідно до низки відповідей тут, - я думаю, що це також повторне використання ключових слів під час гри - запускайте це за певних умов .
JL Peyret

1
@Falanwe, є різниця, коли код закривається break. Канонічний випадок використання - це коли цикл щось шукає, і розривається, коли його знаходить. Виконання elseвиконується лише в тому випадку, якщо нічого не знайдено.
alexis

Відповіді:


212

(На це надихає відповідь @Mark Tolonen.)

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

Це правило відповідає поведінці, яку ви описали:

  • У звичайному виконанні цикл while декілька разів запускається до тих пір, поки умова не оціниться як false, і, отже, природно, що виходить з циклу, запускається інше.
  • Виконуючи breakоператор, ви виходите з циклу, не оцінюючи умову, тому умова не може оцінюватися як хибна, і ви ніколи не запускаєте інше.
  • Виконуючи continueоператор, ви знову оцінюєте умову і робите саме те, що зазвичай на початку ітерації циклу. Отже, якщо умова справжня, ви продовжуєте циклічно, але якщо вона помилкова, запускаєте інше.
  • Інші методи виходу з циклу, такі як, наприклад return, не оцінюють стан і, отже, не виконують інше.

forпетлі ведуть себе так само. Просто вважайте умову істинною, якщо в ітераторі більше елементів, або в іншому випадку помилковим.


8
Це найкраща відповідь. Ставтеся до петель, як до серії тверджень elif, а інша поведінка викриє її природну логіку.
Номенатор

1
Мені також подобається ця відповідь, але вона не проводить аналогію з низкою elifтверджень. Є відповідь, яка це робить, і вона має одне чисте оновлення.
alexis

2
ну не точно, цикл у той час, коли умова може відповідати False безпосередньо перед цим breaks, у такому випадку the elseне запускається, але умова є False. Аналогічно з forпетлями це може бути і breakна останньому елементі.
Tadhg McDonald-Jensen

36

Краще подумати про це так: elseБлок завжди буде виконуватися, якщо в попередньому блоці все піде правильно, щоб досягти forвиснаження.

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


Загальний випадок використання знайдеться під час пошуку предмета в записі iterable, для якого пошук або відміняється, коли предмет знайдено, або "not found"прапор піднімається / друкується через наступний elseблок:

for items in basket:
    if isinstance(item, Egg):
        break
else:
    print("No eggs in basket")  

A continueне викрадає керування з for, тому контроль буде переходити до того, elseяк forвичерпано.


20
Звучить дуже приємно ... але тоді ви очікуєте, що elseзастереження буде виконане, коли все не піде нормально, чи не так? Я вже знову плутаюсь ...
alexis

Я повинен не погоджуватися з вами на "Технічно, це не [семантично схоже на будь-яке інше else]", оскільки elseтест виконується, коли жодна з умов циклу for не визначає True, як я демонструю у своїй відповіді
Tadhg McDonald- Йенсен

@ TadhgMcDonald-Jensen Ви також можете зламати цикл на a False. Таким чином, питання про те , як forбуде зламана залежить від випадку використання.
Мойсей Коледоєй

Правильно, я прошу способу якось співвіднести те, що відбувається з англійським значенням "else" (що насправді відображається в інших цілях використання elsepython). Ви надаєте хороший інтуїтивний підсумок того, що elseробить, @Moses, але не про те, як ми могли б пов’язати цю поведінку з "іншим". Якщо було б використано інше ключове слово (наприклад, nobreakяк зазначено у цій відповіді на відповідне запитання), було б легше це зрозуміти.
alexis

1
Це насправді не має нічого спільного з тим, щоб "справи йшли правильно". Інше виконується лише тоді, коли if/ whileумова оцінюється як помилкове або forнемає елементів. breakіснує цикл, що містить (після else). continueповертається назад і знову оцінює стан циклу.
Марк Толонен

31

Коли ifвиконується виконання else? Коли його стан помилковий. Це точно те саме для while/ else. Таким чином, ви можете думати про while/ elseяк просто про ifте, що продовжує виконувати справжній стан, поки не оцінить помилкове. А breakце не змінить. Він просто вискакує з циклу, що містить, без оцінки. elseВиконується тільки при оцінці в if/ whileумова помилкова.

Схоже for, за винятком помилкового стану, що виснажує його ітератор.

continueі breakне виконувати else. Це не їхня функція. breakВиходить з містить петлі. continueСходить до верхньої частини , що містить петлі, де обчислюється умова циклу. Це акт оцінки if/ whileпомилкового (або forне має більше предметів), який виконується, elseі жодним іншим способом.


1
Те, що ви говорите, звучить дуже розумно, але групування трьох умов припинення разом, "поки [умова] не відповідає або не порушиться / продовжується", є помилковим: важливо, що elseпункт виконується, якщо цикл закінчується continue(або нормально), але не, якщо ми виходимо з break. Ці тонкощі, чому я намагаюся по-справжньому громіти, що elseловить, а що ні.
alexis

4
@ Алексис, так, мені там потрібно було уточнити. Відредаговано. продовження не виконує інше, але повертається до вершини циклу, який може потім оцінити значення false.
Марк Толонен

24

Це те, що по суті означає:

for/while ...:
    if ...:
        break
if there was a break:
    pass
else:
    ...

Це приємніший спосіб написання цього поширеного шаблону:

found = False
for/while ...:
    if ...:
        found = True
        break
if not found:
    ...

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

continueне має нічого спільного з цим питанням. Це призводить до того, що поточна ітерація циклу закінчується, що може трапитися з цілим циклом, і, очевидно, у цьому випадку цикл не закінчився символом a break.

try/else подібний:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...

20

Якщо ви думаєте про свої петлі як структуру, подібну до цієї (дещо псевдокод):

loop:
if condition then

   ... //execute body
   goto loop
else
   ...

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

Тож зауважте, що else виконується лише те, коли перевіряється умова . Це означає, що якщо ви виходите з тіла циклу в середині виконання за допомогою, наприклад, a returnабо a break, оскільки умова не перевіряється ще раз,else справа не буде виконана.

А continueз іншого боку зупиняє поточне виконання, а потім стрибає назад, щоб знову перевірити стан циклу, через що elseможна досягти цього сценарію.


Мені дуже подобається ця відповідь, але ви можете спростити: опустіть endетикетку і просто покладіть goto loopвсередину ifкорпусу. Можливо, навіть застаріло, поставивши ifна ту саму лінію, що і етикетка, і це раптом дуже схоже на оригінал.
Бергі

@Bergi Так, я думаю, це робить трохи зрозуміліше, дякую.
Кейван

15

Моя ситуація з положенням циклу elseбула, коли я спостерігав за розмовою Реймонда Хеттінгера , який розповів історію про те, як він вважав, що його слід було викликати nobreak. Погляньте на наступний код: як ви думаєте, що це зробить?

for i in range(10):
    if test(i):
        break
    # ... work with i
nobreak:
    print('Loop completed')

Що б ти здогадався, що це робить? Ну, частина, яка говорить nobreak, буде виконуватися лише в тому випадку, якщо breakоператор не потрапив у цикл.


8

Зазвичай я схильний думати про подібну структуру:

for item in my_sequence:
    if logic(item):
        do_something(item)
        break

Щоб бути схожим на змінну кількість if/elifвисловлювань:

if logic(my_seq[0]):
    do_something(my_seq[0])
elif logic(my_seq[1]):
    do_something(my_seq[1])
elif logic(my_seq[2]):
    do_something(my_seq[2])
....
elif logic(my_seq[-1]):
    do_something(my_seq[-1])

У цьому випадку elseоператор на циклі for працює точно так само, як і elseу ланцюжку elifs, він виконується лише в тому випадку, якщо жодна з умов перед його оцінкою не відповідає True. (або розірвати виконання з returnвинятком) Якщо мій цикл не відповідає цій специфікації, я зазвичай вирішу відмовитися від використання саме for: elseз тієї причини, яку ви опублікували це питання: це не інтуїтивно зрозуміло.


Правильно. Але цикл працює кілька разів, тому трохи незрозуміло, як ви маєте на увазі застосувати це до циклу for. Ви можете уточнити?
alexis

@alexis Я переробив свою відповідь, я думаю, це зараз набагато зрозуміліше.
Tadhg McDonald-Jensen

7

Інші вже пояснили механіку while/for...else, і посилання на мову Python 3 має авторитетне визначення (див. Час і за ), але ось моя особиста мнемологія - FWIW. Я думаю, головним для мене було розбити це на дві частини: одну для розуміння значення терміна elseвідносно циклу та умову для розуміння управління циклом.

Я вважаю, що найпростіше почати з розуміння while...else:

whileу вас є більше предметів, робіть речі, elseякщо ви закінчитеся, робіть це

for...elseМнемонічні в основному те ж саме:

forкожен предмет, робити речі, але elseякщо ви закінчитеся, зробіть це

В обох випадках elseчастина досягається лише після того, як більше елементів не обробляється, а останній елемент обробляється регулярно (тобто немає breakабо return). continueПросто повертається і бачить , якщо є якісь - або інші елементи. Моя мнемонічність щодо цих правил застосовується як whileдо for:

коли breakING або returnING, немає нічого , elseщоб зробити,
і коли я кажу continue, що це «петля назад , щоб почати» для Вас

- маючи на увазі "цикл назад до початку", очевидно, початок циклу, де ми перевіряємо, чи є ще якісь елементи в ітерабелі, тому що elseстосується continueсправді , це взагалі не грає ніякої ролі.


4
Я б запропонував, що це може бути покращено, сказавши, що звичайна мета циклу for / else - вивчити предмети, поки ви не знайдете те, що шукаєте і хочете зупинити , або якщо у вас не залишиться предметів. Існує "інше" для обробки частини "у вас закінчилися елементи (не знайшовши те, що ви шукали)".
supercat

@supercat: Можливо, але я не знаю, які найпоширеніші способи використання там. elseМоже також використовуватися , щоб зробити що - то , коли ви просто закінчили з усіма пунктами. Приклади включають запис журналу, оновлення користувальницького інтерфейсу або сигналізацію про якийсь інший процес, який ви закінчили. Що-небудь, насправді. Крім того, деякі фрагменти коду мають "успішний" випадок справи breakвсередині циклу, і elseвін використовується для обробки випадку "помилки", коли під час ітерації ви не знайшли підходящого елемента (можливо, саме так ви думали з?).
Фабіан Фагергольм

1
Справа, про яку я думав, була саме тим випадком, коли успішна справа закінчується «перервою», а «інше» справляється з відсутністю успіху. Якщо в циклі немає "розриву", код "else" також може просто слідувати за циклом як частина блоку, що вкладається.
supercat

Якщо тільки вам не потрібно розрізняти випадок, коли цикл пройшов усі ітерабельні елементи без перерви (і це був успішний випадок), і той випадок, коли цього не відбулося. Тоді вам слід покласти "фіналізуючий" код у elseблок циклу або відстежувати результат за допомогою інших засобів. Я в основному погоджуюся, я просто кажу, що я не знаю, як люди використовують цю функцію, і тому я хотів би уникати припущень про те, що elseсценарій " обробляє успішний випадок" чи " elseрозглядає невдалий випадок" є більш поширеним. Але у вас є хороший момент, тому коментар обґрунтований!
Фабіан Фагергольм

7

У тестовій розробці (TDD), коли використовується пріоритет трансформації парадигми ви розглядаєте петлі як узагальнення умовних висловлювань.

Цей підхід добре поєднується з цим синтаксисом, якщо розглядати лише прості if/else(ні elif) твердження:

if cond:
    # 1
else:
    # 2

узагальнює:

while cond:  # <-- generalization
    # 1
else:
    # 2

чудово.

Іншими мовами, кроки TDD від одного випадку до справ зі збірниками потребують більшого рефакторингу.


Ось приклад з 8-го блогу :

У пов'язаній статті на 8-му блозі розглядаються ката програми Word Wrap: додавання рядків до рядків ( sзмінна у фрагментах нижче), щоб вони відповідали заданій ширині ( lengthзмінна у фрагментах нижче). В один момент реалізація виглядає наступним чином (Java):

String result = "";
if (s.length() > length) {
    result = s.substring(0, length) + "\n" + s.substring(length);
} else {
    result = s;
}
return result;

і наступний тест, який наразі не вдається, це:

@Test
public void WordLongerThanTwiceLengthShouldBreakTwice() throws Exception {
    assertThat(wrap("verylongword", 4), is("very\nlong\nword"));
    }

Отже, у нас є код, який працює умовно: коли виконується певна умова, додається розрив рядка. Ми хочемо вдосконалити код для обробки кількох розривів рядків. Рішення, представлене у статті, пропонує застосувати (якщо-> поки) трансформацію , однак автор робить коментар, що:

Хоча циклі не можуть мати elseпропозиції, тому нам потрібно усунути elseшлях, роблячи менше вif шляху. Знову ж таки, це рефакторинг.

що змушує зробити більше змін у коді в контексті одного невдалого тесту:

String result = "";
while (s.length() > length) {
    result += s.substring(0, length) + "\n";
    s = s.substring(length);
}
result += s;

У TDD ми хочемо написати якомога менше коду для проходження тестів. Завдяки синтаксису Python можливе наступне перетворення:

від:

result = ""
if len(s) > length:
    result = s[0:length] + "\n"
    s = s[length:]
else:
    result += s

до:

result = ""
while len(s) > length:
    result += s[0:length] + "\n"
    s = s[length:]
else:
    result += s

6

Я бачу це, else: спрацьовує, коли ви повторюєте повз кінець циклу.

Якщо ви breakабо returnчи raiseви не ітерація в кінці минулого циклу, ви перестаєте immeadiately, і , таким чином, else:блок не буде працювати. Якщо ви continueвсе-таки повторіть кінець циклу, оскільки продовжуйте просто переходити до наступної ітерації. Це не зупиняє цикл.


1
Мені це подобається, я думаю, ти щось на те. Це трохи пов'язує з тим, як циклічне використання раніше було здійснено в старі погані часи до циклу. (А саме: чек був розміщений у нижній частині циклу, а gotoвершина - успіх.) Але це скорочена версія відповіді, що голосує на вершині ...
alexis

@ Алексис, суб'єктивний, але мені здається, що простіше думати.
Вінстон Еверт

насправді я згоден. Хоча б тому, що це пітьє.
alexis

4

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

Але насправді моє ментальне відображення просто в тому, що це "структурована" версія шаблону C / C ++ шаблону:

  for (...) {
    ...
    if (test) { goto done; }
    ...
  }
  ...
done:
  ...

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

(Я поклав "структурований" в лапки, тому що різниця полягає не в тому, чи код структурований чи неструктурований, а лише в тому, чи є ключові слова та граматика, присвячені конкретній структурі)


1
Де знаходиться else? Якщо ви мали на увазі done:етикетку проксі-сервера else:, я вважаю, що це точно назад.
alexis

@alexis «ще» код заповнить в «...» безпосередньо перед на done:етикетці. Загальна відповідність, мабуть, найкраще сказана таким чином: Python має elseконструкцію -on-loop, щоб ви могли висловити цю схему потоку управління без goto.
zwol

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

2

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

Дозвольте продемонструвати це людською мовою.

forрозслідування ifзлочинців є кожною особою в групі підозрюваних кого-небудь break. elseповідомляти про збій.


1

Те, як я думаю про це, головне - це розглянути сенс, continueа неelse .

Інші ключові слова, які ви згадуєте, вириваються з циклу (виходять ненормально), в той час як continue це не так, вони просто пропускають залишок блоку коду всередині циклу. Той факт, що він може передувати закінченню циклу, є випадковим: завершення фактично робиться звичайним способом шляхом оцінки умовного виразу циклу.

Тоді вам просто потрібно пам’ятати, що elseстаття виконується після нормального завершення циклу.


0
# tested in Python 3.6.4
def buy_fruit(fruits):
    '''I translate the 'else' below into 'if no break' from for loop '''
    for fruit in fruits:
        if 'rotten' in fruit:
            print(f'do not want to buy {fruit}')
            break
    else:  #if no break
        print(f'ready to buy {fruits}')


if __name__ == '__main__':
    a_bag_of_apples = ['golden delicious', 'honeycrisp', 'rotten mcintosh']
    b_bag_of_apples = ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
    buy_fruit(a_bag_of_apples)
    buy_fruit(b_bag_of_apples)

'''
do not want to buy rotten mcintosh
ready to buy ['granny smith', 'red delicious', 'honeycrisp', 'gala', 'fuji']
'''
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.