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.
(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:
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.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.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.
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.
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.
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:
...
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.
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.
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 Elif
s. 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, aberelse
, 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
break
ing oderreturn
ing, gibt es nichtselse
zu tun,
und wenn ichcontinue
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.
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 denelse
-Pfad entfernen, indem Sie imif
-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
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.
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.)
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ächtigenif
Jeder ist der Verbrecherbreak
die Untersuchung.else
Fehler melden.
else
ist kaum nützlich, wenn break
in der for
-Schleife ohnehin nicht vorhanden ist.
# 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']
'''
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.