757 Stimmen

Warum verwendet Python "else" nach for- und while-Schleifen?

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

Aber ich verstehe nicht, warum else wird hier als Schlüsselwort verwendet, da es nahelegt, dass der betreffende Code nur ausgeführt wird, wenn die for Block wird nicht abgeschlossen, was das Gegenteil von dem ist, was er tut! Egal, wie ich darüber denke, mein Gehirn kann nicht nahtlos von der for Anweisung an den else blockieren. Für mich, continue o continuewith wäre sinnvoller (und ich versuche, mich darauf einzustellen, es so zu lesen).

Ich frage mich, wie Python-Programmierer dieses Konstrukt in ihrem Kopf (oder laut, wenn Sie wollen) lesen. Vielleicht übersehe ich etwas, das solche Codeblöcke leichter entzifferbar machen würde?


Diese Frage bezieht sich auf die <em>zugrundeliegende Entwurfsentscheidung </em>d.h. <em>warum sie nützlich ist </em>um diesen Code schreiben zu können. Siehe auch <a href="https://stackoverflow.com/questions/3295938">Else-Klausel in der Python-Anweisung while </a>für die spezifische Frage nach <em>was die Syntax bedeutet </em>.

895voto

Lance Helsten Punkte 8879

Ein übliches Konstrukt ist es, eine Schleife laufen zu lassen, bis etwas gefunden wird, und dann die Schleife zu verlassen. Das Problem ist, dass ich feststellen muss, was passiert ist, wenn ich aus der Schleife ausbreche oder die Schleife endet. Eine Methode besteht darin, ein Flag oder eine Speichervariable zu erstellen, mit der ich einen zweiten Test durchführen kann, um festzustellen, wie die Schleife verlassen wurde.

Nehmen wir zum Beispiel an, dass ich eine Liste durchsuchen und jedes Element verarbeiten muss, bis ein Markierungselement gefunden wird, und dann die Verarbeitung beenden muss. Wenn das Markierungselement fehlt, muss eine Ausnahme ausgelöst werden.

Verwendung der Python for ... else Konstrukt haben Sie

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 die raise ist eng an die for-Schleife gebunden, mit der sie arbeitet. Im zweiten Fall ist die Bindung nicht so stark und es können bei der Wartung Fehler auftreten.

408voto

Björn Lindqvist Punkte 17705

Selbst für erfahrene Python-Programmierer ist das ein seltsames Konstrukt. Wenn es in Verbindung mit for-Schleifen verwendet wird, bedeutet es im Grunde "Finde ein Element in der Iterable, wenn keins gefunden wurde, mache ...". Wie in:

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

Aber 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.')

Sie ist semantisch nicht äquivalent zu den anderen beiden Versionen, aber sie funktioniert gut genug in nicht-leistungskritischem Code, bei dem es keine Rolle spielt, ob man die gesamte Liste iteriert oder nicht. Andere mögen anderer Meinung sein, aber ich persönlich würde es vermeiden, die for-else- oder while-else-Blöcke jemals in produktivem Code zu verwenden.

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

270voto

Air Punkte 7712

Es gibt eine ausgezeichnete Präsentation von Raymond Hettinger mit dem Titel Code in schönes, idiomatisches Python umwandeln in dem er kurz auf die Geschichte der for ... else konstruieren. Der entsprechende Abschnitt lautet "Unterscheidung mehrerer Exit-Points in Schleifen". ab 15:50 Uhr und etwa drei Minuten lang fortgesetzt. Hier sind die Höhepunkte:

  • En for ... else Konstrukt wurde von Donald Knuth als Ersatz für bestimmte GOTO Anwendungsfälle;
  • Wiederverwendung der else Schlüsselwort machte Sinn, weil "es das war, was Knuth benutzte, und die Leute wussten, zu dieser Zeit, alle [ for Aussagen] hatten eine if y GOTO darunter, und sie erwarteten die else ;"
  • Im Nachhinein betrachtet hätte es "no break" (oder vielleicht "nobreak") heißen sollen, dann wäre es nicht so verwirrend gewesen.

Wenn die Frage also lautet: "Warum ändern sie dieses Schlüsselwort nicht?", dann Cat Plus Plus hat wahrscheinlich die genaueste Antwort gegeben - Zum jetzigen Zeitpunkt wäre es zu destruktiv für den bestehenden Code, um praktikabel zu sein. Aber wenn die Frage, die Sie wirklich stellen, lautet, warum else wiederverwendet wurde, schien es damals eine gute Idee zu sein.

Ich persönlich mag den Kompromiss, dass man Kommentare abgibt # no break in-line, wo die else könnte auf den ersten Blick als innerhalb der Schleife liegend angesehen werden. Es ist ziemlich klar und prägnant. Diese Option wird kurz erwähnt in die von Björn verlinkte Zusammenfassung am Ende seiner Antwort:

Der Vollständigkeit halber sollte ich erwähnen, dass mit einer kleinen Änderung in Syntax, Programmierer, die diese Syntax wollen, sie jetzt haben können:

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

* Bonuszitat aus diesem Teil des Videos: "Genau wie wenn wir lambda aufrufen <em>makefunction, </em>Niemand würde fragen: 'Was macht Lambda?'"

70voto

Ad Infinitum Punkte 3272

Um es einfach zu machen, können Sie es sich so vorstellen;

  • Wenn es auf die break Befehl in der for Schleife, die else Teil wird nicht aufgerufen.
  • Wenn sie nicht auf die break Befehl in der for Schleife, die else Teil aufgerufen werden.

Mit anderen Worten, wenn die Iteration der for-Schleife nicht "gebrochen" wird mit break die else Teil aufgerufen werden.

47voto

Cat Plus Plus Punkte 119072

Denn sie wollten kein neues Schlüsselwort in die Sprache einführen. Jedes Schlüsselwort stiehlt einen Bezeichner und führt zu Problemen mit der Rückwärtskompatibilität, so dass dies normalerweise der letzte Ausweg ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X