Як я можу зрозуміти пункт `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.