it-swarm.com.de

Warum verwendet Python nach and while-Schleifen 'else'?

Ich verstehe, wie dieses Konstrukt funktioniert:

for i in range(10):
    print(i)

    if i == 9:
        print("Too big - I'm giving up!")
        break;
else:
    print("Completed successfully")

Ich verstehe nicht, warum hier else als Schlüsselwort verwendet wird, da der betreffende Code nur dann ausgeführt wird, wenn der for-Block nicht abgeschlossen ist. Dies ist das Gegenteil von dem, was er tut! Egal wie ich darüber nachdenke, mein Gehirn kann nicht nahtlos von der for-Anweisung zum else-Block übergehen. Für mich wäre continue oder continuewith sinnvoller (und ich versuche mich zu trainieren, es als solches zu lesen).

Ich frage mich, wie Python-Codierer dieses Konstrukt in ihrem Kopf lesen (oder laut, wenn Sie möchten). Vielleicht fehlt mir etwas, das solche Codeblöcke leichter entschlüsseln könnte.

330
Kent Boogaart

Selbst für erfahrene Python-Codierer ist es ein seltsames Konstrukt. Wenn es in Verbindung mit for-loops verwendet wird, bedeutet dies im Wesentlichen "etwas in der Iteration finden, sonst, wenn nichts gefunden wurde ...". Wie in:

found_obj = None
for obj in objects:
    if obj.key == search_key:
        found_obj = obj
        break
else:
    print('No object found.')

Immer wenn Sie dieses Konstrukt sehen, ist es eine bessere Alternative, entweder die Suche in einer Funktion zu kapseln:

def find_obj(search_key):
    for obj in objects:
        if obj.key == search_key:
            return obj

Oder verwenden Sie ein Listenverständnis:

matching_objs = [o for o in objects if o.key == search_key]
if matching_objs:
    print('Found {}'.format(matching_objs[0]))
else:
    print('No object found.')

Es ist nicht semantisch äquivalent zu den beiden anderen Versionen, funktioniert aber in nicht leistungskritischem Code gut, wobei es egal ist, ob Sie die gesamte Liste durchlaufen oder nicht. Andere sind vielleicht anderer Meinung, aber ich persönlich würde es vermeiden, die for-else- oder while-else-Blöcke im Produktionscode zu verwenden. 

Siehe auch [Python-ideas] Zusammenfassung von for ... else Threads

202

Ein übliches Konstrukt besteht darin, eine Schleife auszuführen, bis etwas gefunden wird, und dann aus der Schleife auszubrechen. Das Problem ist, dass, wenn ich aus der Schleife ausbrechen oder die Schleife endet, ich feststellen muss, welcher Fall passiert ist. Eine Methode ist das Erstellen einer Flag- oder Store-Variablen, mit der ich einen zweiten Test durchführen kann, um zu sehen, wie die Schleife beendet wurde.

Nehmen Sie beispielsweise an, dass ich eine Liste durchsuchen und jedes Element bearbeiten muss, bis ein Flagelement gefunden wird, und dann die Verarbeitung anhalten. Wenn das Flag-Element fehlt, muss eine Ausnahme ausgelöst werden.

Verwenden Sie das Python-Konstrukt for...else

for i in mylist:
    if i == theflag:
        break
    process(i)
else:
    raise ValueError("List argument missing terminal flag.")

Vergleichen Sie dies mit einer Methode, die diesen syntaktischen Zucker nicht verwendet:

flagfound = False
for i in mylist:
    if i == theflag:
        flagfound = True
        break
    process(i)

if not flagfound:
    raise ValueError("List argument missing terminal flag.")

Im ersten Fall ist raise fest an die for-Schleife gebunden, mit der es arbeitet. Im zweiten Fall ist die Bindung nicht so stark und es können Fehler bei der Wartung auftreten.

402
Lance Helsten

Es gibt eine ausgezeichnete Präsentation von Raymond Hettinger mit dem Titel Transforming Code in Beautiful, Idiomatic Python , in der er kurz auf die Geschichte des for ... else-Konstrukts eingeht. Der relevante Abschnitt ist "Mehrere Endpunkte in Schleifen unterscheiden" beginnend um 15:50 Uhr und etwa drei Minuten lang. Hier sind die Höhepunkte:

  • Das for ... else-Konstrukt wurde von Donald Knuth als Ersatz für bestimmte GOTO-Anwendungsfälle entwickelt.
  • Die Verwendung des else-Schlüsselworts machte Sinn, da "es das war, was Knuth verwendete und die Leute damals wussten, dass alle [for-Anweisungen] eine if und eine GOTO darunter hatten, und sie erwarteten die else;"
  • Im Nachhinein hätte es "no break" (oder möglicherweise "nobreak") heißen sollen, und dann wäre es nicht verwirrend. *

Wenn also die Frage lautet: Warum ändern sie dieses Keyword nicht? dann Cat Plus Plus gab wahrscheinlich die genaueste Antwort - zu diesem Zeitpunkt wäre es für den vorhandenen Code zu zerstörerisch, um praktisch zu sein. Aber wenn Sie wirklich die Frage stellen, warum else überhaupt wiederverwendet wurde, nun, anscheinend schien es damals eine gute Idee zu sein.

Persönlich mag ich den Kompromiss, # no break inline zu kommentieren, wo die else auf einen Blick als innerhalb der Schleife liegende Person verwechselt werden könnte. Es ist einigermaßen klar und prägnant. Diese Option wird in der Zusammenfassung, die Bjorn verlinkt hat am Ende seiner Antwort kurz erwähnt:

Der Vollständigkeit halber sollte ich das mit einer geringfügigen Änderung in .__ erwähnen. Syntax, Programmierer, die diese Syntax haben möchten, können sie jetzt haben:

for item in sequence:
    process(item)
else:  # no break
    suite

* Bonus-Zitat aus diesem Teil des Videos: "So wie wir Lambda Makefunktion anrufen würden, würde niemand fragen:" Was macht Lambda? "

134
Air

Weil sie kein neues Stichwort in die Sprache einführen wollten. Jeder stiehlt einen Bezeichner und verursacht Kompatibilitätsprobleme mit der Rückwärtskompatibilität. Daher ist dies normalerweise ein letzter Ausweg.

30
Cat Plus Plus

Am einfachsten fand ich heraus, was das for/else getan hat, und was noch wichtiger ist, wann man es benutzt, war, mich darauf zu konzentrieren, wo die break-Anweisung springt. Das For/else-Konstrukt ist ein einzelner Block. Die Pause springt aus dem Block und springt so über die else-Klausel. Wenn der Inhalt der else-Klausel einfach der for-Klausel folgt, wird sie niemals übersprungen, und die entsprechende Logik müsste bereitgestellt werden, indem sie in ein if eingefügt wird. Dies wurde bereits gesagt, aber nicht ganz in diesen Worten. Es kann also jemand anderem helfen. Führen Sie das folgende Codefragment aus. Ich bin aus ganzem Herzen für die Klarheit des "No Break" -Kommentars.

for a in range(3):
    print(a)
    if a==4: # change value to force break or not
        break
else: #no break  +10 for whoever thought of this decoration
    print('for completed OK')

print('statement after for loop')
14
Neil_UK

Ich habe etwas gelesen wie:

Wenn noch die Bedingungen für die Ausführung der Schleife erfüllt sind, machen Sie Sachen, else etwas anderes.

12
pcalcao

Ich denke, die Dokumentation hat eine gute Erklärung für else, continue

[...] es wird ausgeführt, wenn die Schleife durch die Erschöpfung der Liste (mit for) beendet wird oder wenn die Bedingung falsch wird (mit while), nicht aber wenn die Schleife durch eine break-Anweisung beendet wird.

Quelle: Python 2-Dokumente: Lernprogramm zum Kontrollfluss

12
Ayan

Um es einfach zu machen, kann man sich das so vorstellen.

  • Wenn es auf den Befehl break in der Schleife for trifft, wird der Teil else nicht aufgerufen.
  • Wenn es nicht auf den Befehl break in der Schleife for trifft, wird der Teil else aufgerufen.

Mit anderen Worten, wenn für die Schleifeniteration nicht mit break "gebrochen" wird, wird der Teil else aufgerufen.

Es ist so einfach. 

11
Ad Infinitum

Da der technische Teil ziemlich genau beantwortet wurde, bezieht sich mein Kommentar nur auf das Verwirrung, das dieses wiederverwertete Schlüsselwort erzeugt.

Da Python eine sehr eloquente Programmiersprache ist, ist der Missbrauch eines Schlüsselworts berüchtigter. Das else Schlüsselwort beschreibt perfekt einen Teil des Ablauf eines Entscheidungsbaums: "Wenn Sie dies nicht können, tun Sie dies (ansonsten)." Es ist impliziert in unserer eigenen Sprache.

Die Verwendung dieses Schlüsselworts mit den Anweisungen while und for führt zu Verwirrung. Der Grund unserer Karriere als Programmierer hat uns gelehrt, dass sich die else -Anweisung in einem Entscheidungsbaum befindet. its logischer Bereich, ein Wrapper, der bedingt einen Pfad zurückgibt, dem gefolgt werden soll. In der Zwischenzeit haben Schleifenanweisungen ein eindeutiges bildliches Ziel, um etwas zu erreichen. Das Ziel wird nach kontinuierlichen Iterationen eines Prozesses erreicht.

if / else gibt einen zu befolgenden Pfad an . Schleifen folgen einem Pfad, bis das "Ziel" erreicht ist .

Das Problem ist, dass else ein Wort ist, das die letzte Option in einer Bedingung klar definiert. Die Semantik des Wortes werden beide von Python und der menschlichen Sprache geteilt , aber das übrige Wort in der menschlichen Sprache ist Nie verwendet, um die Aktionen anzuzeigen, die jemand oder etwas nach Abschluss eines Vorgangs ausführen wird. Es wird verwendet, wenn während des Abschlussvorgangs ein Problem auftritt (eher wie eine break -Anweisung) ).

Am Ende bleibt das Schlüsselwort in Python. Es ist klar, dass es ein Fehler war, klarer, wenn jeder Programmierer versucht, eine Geschichte zu erfinden, um ihre Verwendung wie ein mnemonisches Gerät zu verstehen. Ich hätte es geliebt, wenn sie stattdessen das Schlüsselwort then gewählt hätten. Ich glaube, dass dieses Schlüsselwort perfekt in diesen iterativen Ablauf passt, die Auszahlung nach der Schleife.

Es ähnelt der Situation, die ein Kind hat, nachdem es jeden Schritt beim Zusammenbauen eines Spielzeugs befolgt hat: Und DANN welcher Vater?

8
3rdWorldCitizen

Ich las es wie "Wenn der iterable vollständig erschöpft ist und die Ausführung nach dem Beenden des for zur nächsten Anweisung übergeht, wird die else-Klausel ausgeführt." Wenn also die Iteration durch break unterbrochen wird, wird diese nicht ausgeführt.

5
0xc0de

Ich bin damit einverstanden, es ist eher ein "Elif nicht [Bedingung (en), der Pause anhebt]".

Ich weiß, dass dies ein alter Thread ist, aber ich schaue gerade in die gleiche Frage und ich bin nicht sicher, ob jemand die Antwort auf diese Frage so aufgenommen hat, wie ich sie verstehe.

Für mich gibt es drei Möglichkeiten, die else in For... else oder While... else-Anweisungen zu "lesen", die alle gleichwertig sind:

  1. else==if the loop completes normally (without a break or error)
  2. else==if the loop does not encounter a break
  3. else==else not (condition raising break) (vermutlich gibt es eine solche Bedingung oder Sie hätten keine Schleife)

Also, im Wesentlichen ist das "else" in einer Schleife wirklich ein "Elif ...", wobei "..." (1) kein Bruch ist, was (2) NOT (Bedingung) entspricht, die den Bruch erhöhen ].

Ich denke, der Schlüssel ist, dass die else ohne den 'break' sinnlos ist. Ein for...else beinhaltet also:

for:
    do stuff
    conditional break # implied by else
else not break:
    do more stuff

Die wesentlichen Elemente einer for...else-Schleife lauten also wie folgt, und Sie würden sie in einfachem Englisch so lesen:

for:
    do stuff
    condition:
        break
else: # read as "else not break" or "else not condition"
    do more stuff

Wie die anderen Poster bereits gesagt haben, wird im Allgemeinen eine Pause ausgelöst, wenn Sie feststellen können, wonach Ihre Schleife sucht. Der else: wird also "Was tun, wenn das Zielelement nicht gefunden wird".

Beispiel

Sie können auch Ausnahmebehandlung, Unterbrechungen und for-Schleifen verwenden.

for x in range(0,3):
    print("x: {}".format(x))
    if x == 2:
        try:
            raise AssertionError("ASSERTION ERROR: x is {}".format(x))
        except:
            print(AssertionError("ASSERTION ERROR: x is {}".format(x)))
            break
else:
    print("X loop complete without error")

Ergebnis

x: 0
x: 1
x: 2
ASSERTION ERROR: x is 2
----------
# loop not completed (hit break), so else didn't run

Beispiel

Ein einfaches Beispiel mit einer Pause.

for y in range(0,3):
    print("y: {}".format(y))
    if y == 2: # will be executed
        print("BREAK: y is {}\n----------".format(y))
        break
else: # not executed because break is hit
    print("y_loop completed without break----------\n")

Ergebnis

y: 0
y: 1
y: 2
BREAK: y is 2
----------
# loop not completed (hit break), so else didn't run

Beispiel

Ein einfaches Beispiel, bei dem es keine Unterbrechung gibt, keine Bedingung, die eine Unterbrechung auslöst und kein Fehler auftritt.

for z in range(0,3):
     print("z: {}".format(z))
     if z == 4: # will not be executed
         print("BREAK: z is {}\n".format(y))
         break
     if z == 4: # will not be executed
         raise AssertionError("ASSERTION ERROR: x is {}".format(x))
else:
     print("z_loop complete without break or error\n----------\n")

Ergebnis

z: 0
z: 1
z: 2
z_loop complete without break or error
----------
4
NotAnAmbiTurner

Codes im else-Anweisungsblock werden ausgeführt, wenn die for-Schleife nicht unterbrochen wurde.

for x in xrange(1,5):
    if x == 5:
        print 'find 5'
        break
else:
    print 'can not find 5!'
#can not find 5!

Aus den docs: break and continue Anweisungen und else Klauseln auf Schleifen

Schleifenanweisungen können eine else-Klausel haben. Sie wird ausgeführt, wenn die Schleife durch die Erschöpfung der Liste (mit for) beendet wird oder wenn die Bedingung falsch wird (mit while), jedoch nicht, wenn die Schleife durch eine break-Anweisung beendet wird. Dies wird durch die folgende Schleife veranschaulicht, die nach Primzahlen sucht:

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             break
...     else:
...         # loop fell through without finding a factor
...         print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

(Ja, dies ist der richtige Code. Schauen Sie genau hin: Die else-Klausel gehört zur for-Schleife, nicht zur if-Anweisung.)

Bei Verwendung mit einer Schleife hat die else-Klausel mehr mit der else-Klausel einer try-Anweisung zu tun als mit if-Anweisungen: Die else-Klausel einer try-Anweisung wird ausgeführt, wenn keine Ausnahme auftritt, und die else-Klausel einer Schleife wird ausgeführt, wenn kein break auftritt . Weitere Informationen zur try-Anweisung und zu Ausnahmen finden Sie unter Behandlung von Ausnahmen.

Die continue-Anweisung, die ebenfalls von C entlehnt wurde, wird mit der nächsten Iteration der Schleife fortgesetzt:

>>> for num in range(2, 10):
...     if num % 2 == 0:
...         print("Found an even number", num)
...         continue
...     print("Found a number", num)
Found an even number 2
Found a number 3
Found an even number 4
Found a number 5
Found an even number 6
Found a number 7
Found an even number 8
Found a number 9
3
GoingMyWay

Das else-Schlüsselwort kann hier verwirrend sein, und wie viele Leute darauf hingewiesen haben, ist etwas wie nobreak, notbreak geeigneter.

Um for ... else ... logisch zu verstehen, vergleichen Sie es mit try...except...else, nicht mit if...else.... Die meisten Python-Programmierer kennen den folgenden Code:

try:
    do_something()
except:
    print("Error happened.") # The try block threw an exception
else:
    print("Everything is find.") # The try block does things just find.

Stellen Sie sich break als eine besondere Art von Exception vor:

for x in iterable:
    do_something(x)
except break:
    pass # Implied by Python's loop semantics
else:
    print('no break encountered')  # No break statement was encountered

Der Unterschied ist python impliziert except break und Sie können es nicht ausschreiben, also wird es zu:

for x in iterable:
    do_something(x)
else:
    print('no break encountered')  # No break statement was encountered

Ja, ich weiß, dass dieser Vergleich schwierig und ermüdend sein kann, aber er verdeutlicht die Verwirrung.

3
cizixs

Man könnte sich wie else wie im Rest des Zeugs oder im übrigen Zeug vorstellen, was nicht in der Schleife gemacht wurde.

2
jamylak

Hier ist ein Weg, darüber nachzudenken, dass ich oben noch niemanden erwähnt habe:

Denken Sie zunächst daran, dass For-Loops im Grunde nur syntaktischer Zucker um While-Loops herum sind. Zum Beispiel die Schleife

for item in sequence:
    do_something(item)

kann (ungefähr) als umgeschrieben werden

item = None
while sequence.hasnext():
    item = sequence.next()
    do_something(item)

Zweitens: Denken Sie daran, dass While-Loops im Grunde nur wiederholte If-Blöcke sind! Sie können eine while-Schleife immer lesen als "Wenn diese Bedingung zutrifft, führen Sie den Körper aus, dann kommen Sie zurück und prüfen Sie noch einmal".

Während/else macht also durchaus Sinn: Es ist genau dieselbe Struktur wie bei/else, mit der zusätzlichen Funktionalität des Loops, bis die Bedingung falsch wird, anstatt die Bedingung nur einmal zu überprüfen.

Und dann macht for/else auch vollkommen Sinn: Da alle for-Loops nur syntaktischer Zucker auf while-Loops sind, müssen Sie nur herausfinden, was die implizite Bedingung der while-Schleife ist Bedingung wird falsch.

2
Aaron Gable

Hier ist ein weiterer idiomatischer Anwendungsfall neben der Suche. Angenommen, Sie wollten warten, bis eine Bedingung wahr ist, z. ein Port, der auf einem Remote-Server geöffnet werden soll, zusammen mit einem Timeout. Dann können Sie ein while...else-Konstrukt wie folgt verwenden:

import socket
import time

sock = socket.socket()
timeout = time.time() + 15
while time.time() < timeout:
    if sock.connect_ex(('127.0.0.1', 80)) is 0:
        print('Port is open now!')
        break
    print('Still waiting...')
else:
    raise TimeoutError()
1

Python verwendet nach for- und while-Schleifen ein else, sodass etwas anderes passiert, wenn für die Schleife nichts zutrifft. Zum Beispiel:

test = 3
while test == 4:
     print("Hello")
else:
     print("Hi")

Die Ausgabe wäre immer und immer wieder "Hi" (wenn ich richtig liege).

0
Mrmongoose64

Nehmen wir an, wir haben eine Funktion

def broken(x) : return False if x==5 else True

Was bedeutet, dass nur 5 nicht kaputt sind. Nun für broken wird niemals mit 5 bewertet: -

for x in range(4):
    if not broken(x) : break
else:
    print("Everything broken... Doom is upon us")

Wird Ausgabe geben: -

Everything broken... Doom is upon us

Wo wann defekt mit 5 bewertet wird: -

for x in range(6):
    if not broken(x) : break
else:
    print("Everything broken... Doom is upon us")

Es wird nichts gedruckt. Indirekt gesagt, gibt es zumindest etwas, das nicht gebrochen ist.

Falls Sie jedoch etwas betrügen und überspringen möchten, wurde festgestellt, dass es defekt war. Setzen Sie also die Schleife fort, auch wenn Sie 5 als fehlerhaft befunden haben, andernfalls wird die Anweisung noch gedruckt. Das ist :-

for x in range(6):
    if not broken(x) : continue
else:
    print("Everything broken... Doom is upon us")

Wird drucken 

Everything broken... Doom is upon us

Ich hoffe es löst die Verwirrung, anstatt eine neue zu schaffen :-)

0

Ich habe einfach selbst versucht, es wieder zu verstehen. Ich habe festgestellt, dass folgendes hilft! 

• Stellen Sie sich die else als mit derifin der Schleife (statt mit der for) gepaart vor. Wenn die Bedingung erfüllt ist, brechen Sie die Schleife, andernfalls - außer einer else, die mit mehreren ifs gekoppelt ist!
• Wenn keine ifs erfüllt wurde, führen Sie die else durch.
• Die mehreren ifs können auch als if-Elifs betrachtet werden!

0
Germaine Goh
for i in range(3):
    print(i)

    if i == 2:
        print("Too big - I'm giving up!")
        break;
else:
    print("Completed successfully")

"sonst" hier ist verrückt einfach, nur gemein 

1, "wenn for clause abgeschlossen ist"

for i in range(3):
    print(i)

    if i == 2:
        print("Too big - I'm giving up!")
        break;
if "for clause is completed":
    print("Completed successfully")

Es reicht aus, so lange Aussagen zu schreiben, wie "für Klausel ist vollendet".

else hier ist ein wenn in seiner Natur.

2, Wie wäre es jedoch mit for clause is not run at all

In [331]: for i in range(0):
     ...:     print(i)
     ...: 
     ...:     if i == 9:
     ...:         print("Too big - I'm giving up!")
     ...:         break
     ...: else:
     ...:     print("Completed successfully")
     ...:     
Completed successfully

Es ist also eine vollständige Kombination von Logik:

if "for clause is completed" or "not run at all":
     do else stuff

oder anders ausgedrückt:

if "for clause is not partially run":
    do else stuff

oder so:

if "for clause not encounter a break":
    do else stuff
0
JawSaw

Gute Antworten sind:

  • dies die die Geschichte erklären, und
  • dies gibt die richtige Zitat, um Ihre Übersetzung/Ihr Verständnis zu erleichtern.

Meine Anmerkung hier stammt von dem, was Donald Knuth einmal gesagt hat (leider kann kein Hinweis gefunden werden), dass es ein Konstrukt gibt, in dem sich while-else nicht von if-else unterscheidet, nämlich (in Python):

x = 2
while x > 3:
    print("foo")
    break
else:
    print("boo")

hat den gleichen Durchfluss (mit Ausnahme geringer Unterschiede):

x = 2
if x > 3:
    print("foo")
else:
    print("boo")

Der Punkt ist, dass if-else als syntaktischer Zucker für while-else betrachtet werden kann, der am Ende seines if-Blocks einen impliziten Bruch aufweist. Die gegenteilige Implikation, dass die while-Schleife eine Erweiterung von if ist, da nur die bedingte Überprüfung wiederholt wird, ist häufiger. Dies ist jedoch unzureichend, wenn Sie über if-else nachdenken, da dies bedeuten würde, dass else block in while-else jedes Mal ausgeführt wird, wenn die Bedingung falsch ist.

Um Ihr Verständnis zu erleichtern, stellen Sie sich das so vor:

Ohne break, return usw. endet die Schleife nur, wenn die Bedingung nicht mehr erfüllt ist (in for müssen Sie for-Schleifen im C-Stil berücksichtigen oder in while übersetzen), und der else-Block wird ausgeführt, wenn die Bedingung false ist.

Noch eine Anmerkung:

Eine vorzeitige break, return usw. innerhalb der Schleife macht es unmöglich, dass die Bedingung falsch wird, weil die Ausführung aus der Schleife gesprungen ist, während die Bedingung wahr war und nie wieder zur Überprüfung kommen würde.

0
WloHu