Jak rozumieć klauzulę „else” pętli Pythona?

191

Wielu programistów Pythona prawdopodobnie nie zdaje sobie sprawy, że składnia whilepętli i forpętli zawiera opcjonalną else:klauzulę:

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

Treść elseklauzuli jest dobrym miejscem dla niektórych rodzajów działań czyszczących i jest wykonywana przy normalnym zakończeniu pętli: tj. Wychodzenie z pętli za pomocą returnlub breakpomijanie elseklauzuli; wyjście po continuewykonaniu go. Wiem, że to tylko dlatego, że po prostu spojrzał go (jeszcze raz), bo nie pamiętam kiedyelse klauzula jest wykonywany.

Zawsze? Na „awarii” pętli, jak sama nazwa wskazuje? Na regularne wypowiedzenie? Nawet jeśli pętla zostanie zakończona za pomocą return? Nigdy nie mogę być całkowicie pewien bez szukania.

Winę za utrzymującą się niepewność elseobarczam wyborem słowa kluczowego: dla tej semantyki uważam to za niezwykle niememoniczne. Moje pytanie nie brzmi „dlaczego to słowo kluczowe jest używane do tego celu” (które prawdopodobnie zagłosowałbym za jego zamknięciem, chociaż dopiero po przeczytaniu odpowiedzi i komentarzy), ale jak mogę myśleć o tym elsesłowie kluczowym, aby jego semantyka miała sens, i ja pamiętasz to?

Jestem pewien, że odbyło się sporo dyskusji na ten temat i mogę sobie wyobrazić, że wybór został dokonany w celu zachowania spójności z klauzulą tryoświadczenia else:(którą również muszę sprawdzić) i aby nie dodawać do listy Zastrzeżone słowa Pythona. Być może powody wyboru elsewyjaśnią jego funkcję i sprawią, że będzie ona bardziej niezapomniana, ale chcę połączyć nazwę z funkcją, a nie po wyjaśnieniach historycznych per se.

Odpowiedzi na to pytanie , które moje pytanie zostało na krótko zamknięte jako duplikat, zawiera wiele interesujących historii. Moje pytanie ma inne znaczenie (jak połączyć konkretną semantykę elsez wyborem słowa kluczowego), ale uważam, że powinien być gdzieś link do tego pytania.

Alexis
źródło
23
Co powiesz na „jeśli jest coś do iteracji ... else”
OneCricketeer
4
Myślę, że pamiętasz to teraz po napisaniu tego pytania :)
Jasper
11
te elseśrodki w zasadzie „jeśli warunek nie kontynuacja”. W tradycyjnej pętli for warunkiem kontynuacji jest zazwyczaj i < 42, w którym to przypadku można wyświetlić tę część jakoif i < 42; execute the loop body; else; do that other thing
njzk2
1
To wszystko prawda, a ja szczególnie lubię odpowiedź drawoc, ale kolejną rzeczą do rozważenia jest to, że jeszcze jest dostępny słowo kluczowe, które również sprawia nieco poczucie syntaxically. Możesz znać try / wyjątek i być może spróbuj / wyjątek / wreszcie, ale ma też inne - uruchom ten kod, jeśli nie wystąpił wyjątek. Co jest btw, a nie to samo, co przerzucanie tego kodu pod klauzulę try - obsługa wyjątków jest najlepsza przy wąskim ukierunkowaniu. Tak więc, chociaż ma to sens koncepcyjny - zgodnie z wieloma odpowiedziami tutaj - myślę, że to słowo kluczowe również jest używane w grze - uruchom to pod pewnymi warunkami .
JL Peyret
1
@Falanwe, istnieje różnica, gdy kod jest opuszczany przez break. Kanoniczny przypadek użycia występuje wtedy, gdy pętla szuka czegoś i pęka, gdy go znajdzie. elseJest wykonywana tylko wtedy, gdy nic nie znaleziono.
Alexis

Odpowiedzi:

212

(Jest to inspirowane odpowiedzią @Mark Tolonen.)

ifOświadczenie prowadzi swoją elseklauzulę jeżeli jej warunek jest fałszywy. Identycznie whilepętla uruchamia klauzulę else, jeśli jej warunek ma wartość false.

Ta reguła pasuje do opisanego zachowania:

  • W normalnym wykonaniu pętla while działa wielokrotnie, dopóki warunek nie zostanie oceniony jako false, a zatem naturalne wyjście z pętli powoduje uruchomienie klauzuli else.
  • Wykonując breakinstrukcję, wychodzisz z pętli bez oceny warunku, więc warunek nie może mieć wartości false i nigdy nie uruchamiasz klauzuli else.
  • Wykonując continueinstrukcję, ponownie oceniasz warunek i robisz dokładnie to, co normalnie na początku iteracji pętli. Tak więc, jeśli warunek jest spełniony, nadal zapętlasz, ale jeśli jest to fałsz, uruchom klauzulę else.
  • Inne metody wyjścia z pętli, takie jak return, nie oceniają warunku i dlatego nie uruchamiają klauzuli else.

forpętle zachowują się w ten sam sposób. Wystarczy uznać warunek za prawdziwy, jeśli iterator zawiera więcej elementów, lub fałsz w przeciwnym razie.

drawoc
źródło
8
To jest doskonała odpowiedź. Traktuj swoje pętle jak serię instrukcji elif, a inne zachowanie ujawni ich naturalną logikę.
Nomenator
1
Podoba mi się również ta odpowiedź, ale nie jest to analogia do szeregu elifstwierdzeń. Odpowiedź na to pytanie brzmi: ma jeden głos w sieci.
Alexis
2
nie do końca, pętla while mogłaby sprawić, że warunek spełniłby warunek False tuż przed nim break, w którym to przypadku elsenie uruchomiłby się, ale warunek jest False. Podobnie w przypadku forpętli może to dotyczyć breakostatniego elementu.
Tadhg McDonald-Jensen
36

Lepiej pomyśleć o tym w ten sposób: elseBlok zawsze będzie wykonywany, jeśli wszystko pójdzie dobrze w poprzednim forbloku, tak że osiągnie wyczerpanie.

Właśnie w tym kontekście oznacza nie exception, nie break, nie return. Wszelkie instrukcje, które przejmują kontrolę for, powodują elseominięcie bloku.


Typowy przypadek użycia występuje przy wyszukiwaniu elementu w iterable, dla którego wyszukiwanie jest albo odwołane, gdy element zostanie znaleziony, albo "not found"flaga zostanie podniesiona / wydrukowana za pomocą następującego elsebloku:

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

continueNie hijack sterowanie z for, więc kontrola przystąpi do elsepo forwyczerpaniu.

Mojżesz Koledoye
źródło
20
Brzmi bardzo ładnie ... ale wtedy można by oczekiwać else, że zostanie wykonana klauzula, gdy coś pójdzie nie tak, prawda? Znowu się mylę ...
Alexis
Muszę się z tobą nie zgodzić w kwestii „Technicznie, to nie jest [semantycznie podobne do siebie else]”, ponieważ elsejest uruchamiane, gdy żaden z warunków w pętli for nie ocenia się na True, jak wykazałem w mojej odpowiedzi
Tadhg McDonald- Jensen
@ TadhgMcDonald-Jensen Możesz także przerwać pętlę na False. Pytanie o to, jak forjest rozbite, zależy od przypadku użycia.
Mojżesz Koledoye
Zgadza się, proszę o sposób, aby w jakiś sposób powiązać to, co dzieje się z angielskim znaczeniem „else” (co rzeczywiście znajduje odzwierciedlenie w innych zastosowaniach elsePythona). elseUdostępniasz dobre intuicyjne podsumowanie tego, co robi @Moses, ale nie pokazuje, w jaki sposób możemy powiązać to zachowanie z „innym”. Gdyby użyć innego słowa kluczowego (np. nobreakJak wspomniano w tej odpowiedzi na powiązane pytanie), łatwiej byłoby zrozumieć.
Alexis
1
To naprawdę nie ma nic wspólnego z „rzeczami, które idą dobrze”. Reszta jest wykonywana wyłącznie, gdy warunek if/ ma whilewartość false lub nie forma elementów. breakistnieje pętla zawierająca (po else). continuewraca i ponownie ocenia stan pętli.
Mark Tolonen,
31

Kiedy ifwykonuje się else? Gdy jego warunek jest fałszywy. Jest dokładnie tak samo dla while/ else. Możesz więc myśleć o while/ elsejako po prostu o iftym, że działa w stanie prawdziwym, dopóki nie oceni wartości false. A breakto nie zmienia. Po prostu wyskakuje z pętli zawierającej bez oceny. elseJest wykonywana tylko wtedy, gdy oceny w if/ whilewarunek jest fałszywy.

forJest podobna, z wyjątkiem jej warunek jest fałszywy wyczerpując swój iterator.

continuei breaknie wykonuj else. To nie jest ich funkcja. breakZamyka pętlę zawierającą. continueSięga do górnej części zawierającej pętli, gdzie warunek pętli jest uwzględniany. Jest to czynność oceniania if/ whilefałszowania (lub fornie ma już żadnych elementów), która jest wykonywana elsei nie ma innego sposobu.

Mark Tolonen
źródło
1
To, co mówisz, brzmi bardzo rozsądnie, ale łączenie trzech warunków zakończenia razem, „dopóki [warunek] nie będzie False lub nie zostanie przerwany / kontynuowany”, jest błędne: Zasadniczo elseklauzula jest wykonywana, jeśli pętla zostanie zakończona continue(lub normalnie), ale nie, jeśli wyjdziemy z break. Te subtelności są powodem, dla którego naprawdę próbuję zrozumieć, co elsełapie, a co nie.
Alexis
4
@alexis tak, musiałem to wyjaśnić. Edytowane. Kontynuuj nie wykonuje reszty, ale wraca na początek pętli, co może następnie dać wynik fałszywy.
Mark Tolonen,
24

Oto, co zasadniczo oznacza:

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

To przyjemniejszy sposób pisania tego wspólnego wzoru:

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

elseKlauzula nie zostanie wykonane, jeśli istnieje return, ponieważ returnliści funkcja, jak to ma na celu. Jedynym wyjątkiem od tego, o czym możesz myśleć, jest to finally, którego celem jest upewnienie się, że zawsze jest wykonywana.

continuenie ma nic wspólnego z tą sprawą. Powoduje to, że bieżąca iteracja pętli kończy się, co może się zdarzyć, aby zakończyć całą pętlę, i oczywiście w tym przypadku pętla nie została zakończona przez break.

try/else jest podobny:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...
Alex Hall
źródło
20

Jeśli myślisz o swoich pętlach jako o strukturze podobnej do tej (nieco pseudo-kod):

loop:
if condition then

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

może to mieć trochę więcej sensu. Pętla jest w gruncie rzeczy tylko ifinstrukcją powtarzaną do momentu spełnienia warunku false. I to jest ważny punkt. Pętla sprawdza swój stan i widzi, że jest false, a zatem wykonuje else(tak jak normalnie if/else), a następnie wykonuje pętlę.

Zauważ więc, że else jedyny get jest wykonywany, gdy warunek jest sprawdzany . Oznacza to, że jeśli wyjdziesz z ciała pętli w trakcie wykonywania, na przykład za pomocą a returnlub a break, ponieważ warunek nie jest ponownie sprawdzany, elsesprawa nie zostanie wykonana.

Z continuedrugiej strony A zatrzymuje bieżące wykonanie, a następnie przeskakuje z powrotem, aby ponownie sprawdzić stan pętli, i dlatego elsemożna to osiągnąć w tym scenariuszu.

Keiwan
źródło
Podoba mi się ta odpowiedź, ale możesz uprościć: Pomiń endetykietę i po prostu włóż goto loopwnętrze if. Może nawet nieprzyzwoity, umieszczając ten ifsam wiersz na etykiecie, i nagle wygląda bardzo podobnie do oryginału.
Bergi,
@Bergi Tak, myślę, że dzięki temu jest trochę jaśniej.
Keiwan
15

Moja chwila z elseklauzulą pętli miała miejsce, gdy oglądałem rozmowę Raymonda Hettingera , który opowiedział historię o tym, jak według niego powinien się nazywać nobreak. Spójrz na poniższy kod, jak myślisz, co by to zrobił?

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

Jak myślisz, co to robi? Cóż, ta część, która mówi, nobreakbyłaby wykonana tylko wtedy, gdyby breakinstrukcja nie została trafiona w pętlę.

nasser-sh
źródło
8

Zwykle myślę o takiej strukturze pętli:

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

Aby być podobnym do zmiennej liczby if/elifinstrukcji:

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])

W tym przypadku elseinstrukcja w pętli for działa dokładnie tak samo, jak elseinstrukcja w łańcuchu elifs, jest wykonywana tylko wtedy, gdy żaden z warunków przed jej oceną na True. (lub przerwanie wykonania z returnlub wyjątek) Jeśli moja pętla nie pasuje do tej specyfikacji, zwykle rezygnuję z używania for: elsez dokładnie tego powodu, dla którego opublikowałeś to pytanie: jest to nieintuicyjne.

Tadhg McDonald-Jensen
źródło
Dobrze. Ale pętla działa wiele razy, więc nie jest jasne, jak zamierzasz zastosować to do pętli for. Możesz wyjaśnić?
Alexis
@alexis Ponownie wykonałem moją odpowiedź, myślę, że teraz jest o wiele jaśniej.
Tadhg McDonald-Jensen
7

Inni już wyjaśnili mechanikę while/for...else, a odniesienie do języka Python 3 ma autorytatywną definicję (patrz podczas i po ), ale oto mój osobisty mnemonik, FWIW. Wydaje mi się, że kluczem było dla mnie podzielenie tego na dwie części: jedną dla zrozumienia znaczenia elserelacji warunkowej w pętli i jedną dla zrozumienia kontroli pętli.

Najłatwiej jest zacząć od zrozumienia while...else:

whilemasz więcej przedmiotów, rób rzeczy, elsejeśli zabraknie, rób to

for...elseMnemonic jest zasadniczo taka sama:

forkażdy przedmiot, rób rzeczy, ale elsejeśli zabraknie, rób to

W obu przypadkach elseczęść jest osiągana tylko wtedy, gdy nie ma już więcej elementów do przetworzenia, a ostatni element został przetworzony w zwykły sposób (tj. Brak breaklub return). Po continueprostu wraca i sprawdza, czy jest więcej przedmiotów. Mój mnemonik tych reguł dotyczy zarówno whilei for:

podczas breaking lub returning nie ma nic elsedo zrobienia,
a kiedy mówię continue, jest to dla ciebie „zapętlenie, aby zacząć”

- „zwrot z powrotem do początku” oznacza oczywiście początek pętli, w której sprawdzamy, czy w iteracji jest więcej elementów, jeśli chodzi o elseto, to continuetak naprawdę nie odgrywa żadnej roli.

Fabian Fagerholm
źródło
4
Sugerowałbym, że można to ulepszyć, mówiąc, że zwykłym celem pętli for / else jest badanie przedmiotów, dopóki nie znajdziesz tego, czego szukasz i chcesz przestać , lub skończy Ci się to. „Else” istnieje, aby poradzić sobie z częścią „zabrakło Ci przedmiotów (bez znalezienia tego, czego szukałeś)”.
supercat
@ superupat: Być może, ale nie wiem, jakie są najczęstsze zastosowania. elseMoże być również wykorzystywany do zrobienia czegoś, gdy jesteś po prostu wykończony z wszystkich przedmiotów. Przykłady obejmują pisanie wpisu dziennika, aktualizację interfejsu użytkownika lub sygnalizowanie innego zakończonego procesu. Cokolwiek, naprawdę. Ponadto niektóre fragmenty kodu zakończone są „udanym” zakończeniem breakwewnątrz pętli i elsesłużą do obsługi przypadku „błędu”, w którym podczas iteracji nie znalazłeś żadnego odpowiedniego elementu (być może właśnie tak myślałeś z?).
Fabian Fagerholm
1
Sprawa, o której myślałem, była dokładnie przypadkiem, w którym udana sprawa kończy się „przerwą”, a „inna” radzi sobie z brakiem sukcesu. Jeśli w pętli nie ma „przerwy”, kod „else” może po prostu podążać za pętlą jako część otaczającego bloku.
supercat
Chyba że musisz rozróżnić przypadek, w którym pętla przechodziła przez wszystkie powtarzalne elementy bez przerwy (i to był udany przypadek) od przypadku, w którym nie. Następnie należy umieścić kod „finalizujący” w elsebloku pętli lub śledzić wynik przy użyciu innych środków. Zasadniczo się zgadzam, mówię tylko, że nie wiem, w jaki sposób ludzie korzystają z tej funkcji, dlatego chciałbym uniknąć zakładania, czy elsescenariusz „ obsługuje przypadek pomyślny”, czy scenariusz „ elseobsługuje przypadek nieudany” jest bardziej powszechny. Ale masz rację, więc komentuj z góry!
Fabian Fagerholm
7

W rozwoju opartym na testach (TDD), używając paradygmatu Priorytet transformacji , traktujesz pętle jako uogólnienie instrukcji warunkowych.

Podejście to dobrze łączy się z tą składnią, jeśli weźmie się pod uwagę tylko proste if/else(nie elif) instrukcje:

if cond:
    # 1
else:
    # 2

uogólnia na:

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

ładnie.

W innych językach kroki TDD od pojedynczej skrzynki do skrzynek ze zbiorami wymagają więcej refaktoryzacji.


Oto przykład z blogu 8thlight :

W powiązanym artykule na blogu 8thlight rozważono kata zawijania słów: dodawanie podziałów wierszy do ciągów ( szmienna we fragmentach poniżej), aby dopasować je do określonej szerokości ( lengthzmienna we fragmentach poniżej). W pewnym momencie implementacja wygląda następująco (Java):

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

a następny test, który obecnie się nie powiedzie, to:

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

Mamy więc kod, który działa warunkowo: po spełnieniu określonego warunku dodawany jest podział wiersza. Chcemy ulepszyć kod, aby obsługiwał wiele podziałów linii. Rozwiązanie przedstawione w artykule proponuje zastosowanie transformacji (if-> while) , jednak autor komentuje:

Pętle nie mogą zawierać elseklauzul, ale musimy wyeliminować elseścieżkę, robiąc mniej na ifścieżce. Ponownie jest to refaktoryzacja.

co zmusza do wprowadzenia większej liczby zmian w kodzie w kontekście jednego testu zakończonego niepowodzeniem:

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

W TDD chcemy napisać jak najmniej kodu, aby testy przebiegły pomyślnie. Dzięki składni Pythona możliwa jest następująca transformacja:

z:

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

do:

result = ""
while len(s) > length:
    result += s[0:length] + "\n"
    s = s[length:]
else:
    result += s
BartoszKP
źródło
6

Tak, jak to widzę, else:odpala, gdy iterujesz za końcem pętli.

Jeśli breaklub returnczy raisenie zrobić iterate poza końcem pętli, ty immeadiately zatrzymać, a więc else:blok nie zostanie uruchomiony. Jeśli continuenadal wykonujesz iterację poza końcem pętli, ponieważ kontynuuj, po prostu przejdź do następnej iteracji. To nie zatrzymuje pętli.

Winston Ewert
źródło
1
Podoba mi się, myślę, że coś masz na myśli. Jest to trochę związane z tym, jak pętle były stosowane w złych, dawnych czasach przed słowami kluczowymi pętli. (Mianowicie: czek został umieszczony na dole pętli, z gotonajwyższym wynikiem ). Ale to krótsza wersja najlepiej głosowanej odpowiedzi ...
Alexis
@alexis, subiektywnie, ale łatwiej mi się zastanowić, jak wyrazić swoją opinię.
Winston Ewert,
właściwie się zgadzam. Choćby dlatego, że jest bardziej zwięzły.
Alexis
4

Pomyśl o elseklauzuli jako o części konstrukcji pętli; breakcałkowicie zrywa z konstrukcji pętli, a tym samym pomija elseklauzulę.

Ale tak naprawdę, moim mapowaniem mentalnym jest po prostu to, że jest to „strukturalna” wersja wzorca C / C ++:

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

Więc kiedy go napotykam for...elselub piszę, zamiast rozumieć go bezpośrednio , mentalnie tłumaczę to na powyższe rozumienie wzorca, a następnie sprawdzam, które części mapy składni Pythona odwzorowują na które części wzorca.

(„Strukturyzowane” umieszczam w przestraszonych cudzysłowach, ponieważ różnica nie polega na tym, czy kod jest ustrukturyzowany czy nieustrukturyzowany, ale tylko na tym, czy dla danej struktury są słowa kluczowe i gramatyka)


źródło
1
Gdzie jest else? Jeśli miałeś na myśli done:etykietę jako proxy lub else:, myślę, że masz ją dokładnie odwrotnie.
Alexis
@alexis do „innego” kod będzie wypełnić w „...” bezpośrednio przed na done:etykiecie. Być może najlepiej jest powiedzieć, że ogólna zgodność: Python ma konstrukcję else-on-loop, dzięki czemu można wyrazić ten wzorzec przepływu sterowania bez goto.
zwolnienie
Istnieją inne sposoby wykonania tego wzorca sterowania, np. Poprzez ustawienie flagi. Tego właśnie elseunika.
Alexis
2

Jeśli połączysz się elsew parę for, może to być mylące. Nie sądzę, aby słowo kluczowe elsebyło doskonałym wyborem dla tej składni, ale jeśli połączysz się elsez tym, ifco zawiera break, możesz zobaczyć, że ma to sens. elsejest mało przydatne, jeśli nie ma poprzedzającego ifoświadczenia i uważam, że właśnie dlatego projektant składni wybrał to słowo kluczowe.

Pokażę to w ludzkim języku.

forkażda osoba z grupy podejrzanych ifjest przestępcą breakw dochodzeniu. elsezgłoś błąd.

bomby
źródło
1

Sposób, w jaki o tym myślę, jest kluczem do rozważenia znaczenia continuezamiastelse .

Inne słowa kluczowe, o których wspominasz, wychodzą z pętli (wychodzą nienormalnie) continue nie, po prostu pomijają resztę bloku kodu wewnątrz pętli. Fakt, że może poprzedzać zakończenie pętli, jest przypadkowy: zakończenie odbywa się w normalny sposób poprzez ocenę wyrażenia warunkowego w pętli.

Musisz tylko pamiętać, że elseklauzula jest wykonywana po normalnym zakończeniu pętli.

Bob Sammers
źródło
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']
'''
W dół strumienia
źródło