it-swarm.com.de

Wie kann ich die "else" -Klausel von Python-Schleifen verstehen?

Vielen Python-Programmierern ist wahrscheinlich nicht bekannt, dass die Syntax von while-Schleifen und for-Schleifen eine optionale else:-Klausel enthält:

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

Der Rumpf der else-Klausel ist ein guter Ort für bestimmte Arten von Bereinigungsaktionen und wird bei normalem Abschluss der Schleife ausgeführt: Das heißt, das Verlassen der Schleife mit return oder break überspringt die else-Klausel. Beenden, nachdem eine continue es ausgeführt hat. Ich weiß das nur, weil ich gerade nachgeschlagen habe (noch einmal), weil ich mich nie an wann erinnern kann, wenn die Klausel else ausgeführt wird.

Immer? Bei "Fehler" der Schleife, wie der Name schon sagt? Bei regelmäßiger Kündigung? Auch wenn die Schleife mit return beendet wird? Ich kann nie ganz sicher sein, ohne nachzusehen.

Ich gebe meiner hartnäckigen Ungewissheit die Wahl des Stichworts zu: Ich finde else für diese Semantik unglaublich unverständlich. Meine Frage ist nicht "Warum wird dieses Keyword für diesen Zweck verwendet" (was ich wahrscheinlich für das Schließen stimmen würde, obwohl ich erst die Antworten und Kommentare gelesen habe), aber wie kann ich über das else-Keyword nachdenken, damit seine Semantik Sinn ergibt und ich kann mich deshalb daran erinnern?

Ich bin mir sicher, dass darüber ziemlich viel diskutiert wurde, und ich kann mir vorstellen, dass die Entscheidung für die Übereinstimmung mit der else:-Klausel der try-Anweisung (die ich auch nachschlagen muss) getroffen wurde, und mit dem Ziel, sie nicht zu ergänzen Liste der reservierten Wörter von Python. Vielleicht klären die Gründe für die Wahl von else ihre Funktion und machen sie einprägsamer, aber ich verstehe den Namen mit der Funktion, nicht nach der historischen Erklärung an sich.

Die Antworten auf diese Frage , von denen meine Frage als Duplikat kurz geschlossen wurde, enthalten viele interessante Hintergründe. Meine Frage hat einen anderen Fokus (wie man die spezifische Semantik von else mit der Schlüsselwortauswahl verbindet), aber ich denke, dass irgendwo ein Link zu dieser Frage sein sollte.

185
alexis

(Dies ist inspiriert von @ Mark Tolonens Antwort.)

Eine if-Anweisung führt ihre else-Klausel aus, wenn ihre Bedingung als falsch ausgewertet wird . Eine while-Schleife führt die else-Klausel aus, wenn ihre Bedingung als falsch ausgewertet wird.

Diese Regel entspricht dem von Ihnen beschriebenen Verhalten:

  • In der normalen Ausführung wird die while-Schleife wiederholt ausgeführt, bis die Bedingung als falsch ausgewertet wird. Daher wird beim Beenden der Schleife natürlich die else-Klausel ausgeführt.
  • Wenn Sie eine break-Anweisung ausführen, verlassen Sie die Schleife, ohne die Bedingung zu bewerten. Daher kann die Bedingung nicht als falsch ausgewertet werden, und Sie führen niemals die else-Klausel aus.
  • Wenn Sie eine continue-Anweisung ausführen, werten Sie die Bedingung erneut aus und machen genau das, was Sie normalerweise zu Beginn einer Schleifeniteration tun würden. Wenn also die Bedingung wahr ist, führen Sie eine Schleife durch die else-Klausel.
  • Andere Methoden zum Verlassen der Schleife, wie zum Beispiel return, werten die Bedingung nicht aus und führen daher keine else-Klausel aus.

for-Schleifen verhalten sich genauso. Betrachten Sie die Bedingung einfach als wahr, wenn der Iterator mehr Elemente enthält oder ansonsten falsch.

211
drawoc

Stellen Sie sich das besser so vor: Der else-Block wird immer ausgeführt, wenn alles im vorherigen for-Block right geht, sodass er erschöpft ist. 

Right bedeutet in diesem Zusammenhang keine exception, keine break, keine return. Jede Anweisung, die von for gesteuert wird, bewirkt, dass der else-Block umgangen wird.


Ein häufiger Anwendungsfall wird bei der Suche nach einem Artikel in einer iterable gefunden, für den die Suche entweder abgerufen wird, wenn der Artikel gefunden wird, oder ein "not found"-Flag über den folgenden else-Block gesetzt/gedruckt wird:

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

Eine continue hijackt die Kontrolle von for nicht, daher geht die Steuerung zur else über, nachdem die for erschöpft ist.

36
Moses Koledoye

Wann führt ein if ein else aus? Wenn sein Zustand falsch ist. Dies gilt auch für while/else. Sie können sich also while/else nur als if vorstellen, das seinen wahren Zustand so lange ausführt, bis es false ergibt. Ein break ändert das nicht. Es springt einfach ohne Auswertung aus der enthaltenden Schleife. Die else wird nur ausgeführt, wenn auswertend die if/while Bedingung falsch ist.

Das for ist ähnlich, außer dass sein falscher Zustand seinen Iterator erschöpft.

continue und break führen else nicht aus. Das ist nicht ihre Funktion. Das break verlässt die enthaltende Schleife. Das continue geht zurück zum Anfang der enthaltenden Schleife, wo die Schleifenbedingung ausgewertet wird. Es ist die Auswertung von if/while zu false (oder for hat keine weiteren Elemente), die else ausführt, und keine andere Methode.

31
Mark Tolonen

Dies bedeutet im Wesentlichen:

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

Es ist eine schönere Art, dieses allgemeine Muster zu schreiben:

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

Die else-Klausel wird nicht ausgeführt, wenn eine return vorhanden ist, da return die Funktion verlässt, wie es beabsichtigt ist. Die einzige Ausnahme von dem, an das Sie denken können, ist finally, dessen Zweck es ist sicherzustellen, dass es immer ausgeführt wird.

continue hat nichts Besonderes mit dieser Angelegenheit zu tun. Dies bewirkt, dass die aktuelle Iteration der Schleife endet, was möglicherweise die gesamte Schleife beendet. In diesem Fall wurde die Schleife offensichtlich nicht durch eine break beendet.

try/else ist ähnlich:

try:
    ...
except:
    ...
if there was an exception:
    pass
else:
    ...
24
Alex Hall

Wenn Sie sich Ihre Schleifen als eine ähnliche Struktur vorstellen (etwas Pseudo-Code):

loop:
if condition then

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

es könnte ein bisschen sinnvoller sein. Eine Schleife ist im Wesentlichen nur eine if-Anweisung, die wiederholt wird, bis die Bedingung false ist. Und das ist der wichtige Punkt. Die Schleife prüft ihren Zustand und sieht, dass es false ist, führt also die else aus (genau wie ein normaler if/else), und dann ist die Schleife abgeschlossen.

Beachten Sie also, dass die else nur ausgeführt wird, wenn die Bedingung geprüft wird . Das heißt, wenn Sie den Rumpf der Schleife während der Ausführung beispielsweise mit einer return oder einer break verlassen, wird der Fall else nicht ausgeführt, da die Bedingung nicht erneut geprüft wird.

Eine continue hingegen stoppt die aktuelle Ausführung und springt dann zurück, um den Zustand der Schleife erneut zu überprüfen, weshalb die else in diesem Szenario erreicht werden kann.

20
Keiwan

Mein größter Moment mit der else-Klausel der Schleife war, als ich einen Vortrag von Raymond Hettinger sah, der eine Geschichte darüber erzählte, wie er gedacht hatte, dass es nobreak heißen sollte. Schauen Sie sich den folgenden Code an. Was würde er Ihrer Meinung nach tun?

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

Was würdest du denken? Nun, der Teil, der nobreak sagt, würde nur ausgeführt, wenn eine break-Anweisung in der Schleife nicht getroffen wurde.

14
nasser-sh

Normalerweise denke ich an eine Schleifenstruktur wie diese:

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

So ähnlich wie eine variable Anzahl von if/Elif-Anweisungen:

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

In diesem Fall funktioniert die else-Anweisung in der for-Schleife genauso wie die else-Anweisung in der Kette von Elifs. Sie wird nur ausgeführt, wenn keine der Bedingungen vor der Auswertung als "Wahr" ausgewertet wird. (oder brechen Sie die Ausführung mit return oder einer Ausnahme ab.) Wenn meine Schleife nicht dieser Spezifikation entspricht, lehne ich die Verwendung von for: else aus dem genauen Grund ab, aus dem Sie diese Frage gestellt haben: Sie ist nicht intuitiv.

Andere haben bereits die Mechanik von while/for...else erläutert, und der Python 3-Referenzcode hat die maßgebliche Definition (siehe while und for ), aber hier ist meine persönliche Mnemonik, FWIW. Ich denke, der Schlüssel für mich war es, dies in zwei Teile zu unterteilen: einen für das Verständnis der Bedeutung von else in Bezug auf die Schleifenbedingung und einen für das Verständnis der Schleifensteuerung.

Ich finde, es ist am einfachsten, while...else zu verstehen:

while Du hast mehr Gegenstände, erledigst was, else, wenn Sie ausgehen, machen Sie dies

Die for...else-Mnemonic ist grundsätzlich gleich:

for jedes Element, erledigen Sie Sachen, aber else, wenn Sie ausgehen, tun Sie dies

In beiden Fällen wird der else-Teil erst erreicht, wenn keine weiteren Elemente mehr verarbeitet werden müssen und der letzte Artikel regelmäßig verarbeitet wurde (d. H. Kein break oder return). Ein continue geht einfach zurück und sieht, ob weitere Artikel vorhanden sind. Meine Mnemonik für diese Regeln gilt sowohl für while als auch für for:

Wenn breaking oder returning, gibt es nichts else zu tun,
und wenn ich continue sage, ist das "loop back to start" für Sie

- mit "Schleife zurück zum Start", was natürlich den Beginn der Schleife bedeutet, bei dem wir prüfen, ob noch weitere Elemente im Iterierbaren vorhanden sind. Was else betrifft, spielt continue wirklich keine Rolle.

6

In Test-Driven Development (TDD) behandeln Sie Schleifen, wenn Sie das Transformation Priority Premise - Paradigma verwenden, als Verallgemeinerung von Bedingungsanweisungen.

Dieser Ansatz lässt sich gut mit dieser Syntax kombinieren, wenn Sie nur einfache if/else (keine Elif) -Anweisungen in Betracht ziehen:

if cond:
    # 1
else:
    # 2

verallgemeinert zu:

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

schön.

In anderen Sprachen erfordert TDD von einem einzelnen Fall zu Fällen mit Sammlungen mehr Refactoring.


Hier ist ein Beispiel aus 8thlight Blog :

In dem verknüpften Artikel auf dem 8thlight-Blog wird die Word-Wrap-Kata berücksichtigt: Hinzufügen von Zeilenumbrüchen zu Zeichenfolgen (die Variable s in den folgenden Ausschnitten), damit sie auf eine bestimmte Breite passen (die Variable length in den folgenden Ausschnitten). An einem Punkt sieht die Implementierung wie folgt aus (Java):

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

und der nächste Test, der derzeit fehlschlägt, ist:

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

Wir haben also Code, der bedingt funktioniert: Wenn eine bestimmte Bedingung erfüllt ist, wird ein Zeilenumbruch hinzugefügt. Wir möchten den Code verbessern, um mehrere Zeilenumbrüche zu behandeln. Die in diesem Artikel vorgestellte Lösung schlägt vor, die (if-> while) -Umwandlung anzuwenden. Der Autor macht jedoch einen Kommentar:

Da Schleifen keine else-Klauseln enthalten können, müssen Sie den else-Pfad entfernen, indem Sie im if-Pfad weniger tun. Dies ist wiederum ein Refactoring.

welche zwingt, im Rahmen eines fehlgeschlagenen Tests weitere Änderungen am Code vorzunehmen:

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

In TDD möchten wir so wenig Code wie möglich schreiben, um Tests zu bestehen. Dank der Python-Syntax ist folgende Transformation möglich:

von:

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

zu:

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

So wie ich es sehe, wird else: ausgelöst, wenn Sie am Ende der Schleife vorbeilaufen.

Wenn Sie break oder return oder raise nicht über das Ende der Schleife hinauslaufen, halten Sie sofort an, und der else:-Block wird nicht ausgeführt. Wenn Sie continue noch immer über das Ende der Schleife hinausgehen, wird mit der nächsten Iteration fortgefahren. Die Schleife wird nicht angehalten. 

5
Winston Ewert

Stellen Sie sich die else-Klausel als Teil des Schleifenkonstrukts vor. break bricht vollständig aus dem Schleifenkonstrukt aus und überspringt somit die else-Klausel.

Mein mentales Mapping ist jedoch einfach die strukturierte Version des C/C++ - Musters:

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

Wenn ich also for...else treffe oder selbst schreibe, anstatt es zu verstehen direkt, übersetze ich es mental in das obige Verständnis des Musters und ermittle dann, welche Teile der Python-Syntax welchen Teilen des Musters zugeordnet sind .

(Ich füge "Scared Quotes" in "Scared Quotes" ein, da der Unterschied nicht darin besteht, ob der Code strukturiert oder unstrukturiert ist, sondern lediglich, ob Schlüsselwörter und Grammatik für die jeweilige Struktur vorgesehen sind.)

3
Hurkyl

Wenn Sie versuchen, else mit for in Ihrem Kopf zu koppeln, kann dies verwirrend sein. Ich glaube nicht, dass das Schlüsselwort else eine gute Wahl für diese Syntax war. Wenn Sie jedoch else mit break koppeln, können Sie feststellen, dass es tatsächlich sinnvoll ist.

Lassen Sie mich das in menschlicher Sprache demonstrieren.

for Jede Person in einer Gruppe von Verdächtigen if Jeder ist der Verbrecher break die Untersuchung. else Fehler melden.


else ist kaum nützlich, wenn break in der for-Schleife ohnehin nicht vorhanden ist.

0
bombs
# 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 __== '__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']
'''
0
Down the Stream

Ich denke darüber nach, der Schlüssel ist, die Bedeutung von continue und nicht else zu berücksichtigen.

Die anderen Schlüsselwörter, die Sie erwähnen, brechen aus der Schleife (abnormales Beenden), während continue dies nicht tut. Sie überspringt nur den Rest des Codeblocks in der Schleife. Die Tatsache, dass es dem Abschluss der Schleife vorangehen kann, ist zufällig: Die Beendigung erfolgt auf normale Weise durch Auswertung des schleifenbedingten Ausdrucks.

Dann müssen Sie nur daran denken, dass die else-Klausel nach dem normalen Abschluss der Schleife ausgeführt wird.

0
Bob Sammers