Eine Do-While-Schleife in Python emulieren?

797

Ich muss eine Do-While-Schleife in einem Python-Programm emulieren. Leider funktioniert der folgende einfache Code nicht:

list_of_ints = [ 1, 2, 3 ]
iterator = list_of_ints.__iter__()
element = None

while True:
  if element:
    print element

  try:
    element = iterator.next()
  except StopIteration:
    break

print "done"

Anstelle von "1,2,3, erledigt" wird die folgende Ausgabe gedruckt:

[stdout:]1
[stdout:]2
[stdout:]3
None['Traceback (most recent call last):
', '  File "test_python.py", line 8, in <module>
    s = i.next()
', 'StopIteration
']

Was kann ich tun, um die Ausnahme "Iteration stoppen" abzufangen und eine while-Schleife ordnungsgemäß zu unterbrechen?

Ein Beispiel dafür, warum so etwas benötigt wird, ist unten als Pseudocode gezeigt.

Zustandsmaschine:

s = ""
while True :
  if state is STATE_CODE :
    if "//" in s :
      tokens.add( TOKEN_COMMENT, s.split( "//" )[1] )
      state = STATE_COMMENT
    else :
      tokens.add( TOKEN_CODE, s )
  if state is STATE_COMMENT :
    if "//" in s :
      tokens.append( TOKEN_COMMENT, s.split( "//" )[1] )
    else
      state = STATE_CODE
      # Re-evaluate same line
      continue
  try :
    s = i.next()
  except StopIteration :
    break
grigoryvp
quelle
4
Ähm ... Das ist kein richtiges "Do-While"; das ist einfach ein "für immer tun". Was ist los mit "while True" und "break"?
S.Lott
70
S. Lott: Ich bin mir ziemlich sicher, dass seine Frage lautete, wie man do in Python implementiert. Ich würde also nicht erwarten, dass sein Code vollständig korrekt ist. Außerdem ist er einer Aufgabe sehr nahe ... er überprüft einen Zustand am Ende der "für immer" -Schleife, um festzustellen, ob er ausbrechen sollte. Es ist nicht "für immer tun".
Tom
4
Also ... Ihr anfänglicher Beispielcode funktioniert für mich tatsächlich ohne Probleme und ich bekomme diesen Traceback nicht. Das ist eine richtige Redewendung für eine do while-Schleife, bei der die Unterbrechungsbedingung die Erschöpfung des Iterators ist. In der Regel würden Sie s=i.next()eher als "Keine" festlegen und möglicherweise erste Arbeiten ausführen, anstatt nur Ihren ersten Durchgang durch die Schleife unbrauchbar zu machen.
Unterlaufen
3
@underrun Leider ist der Beitrag nicht mit der Version von Python versehen, die verwendet wurde - das ursprüngliche Snippet funktioniert auch für mich mit 2.7, vermutlich aufgrund von Aktualisierungen der Python-Sprache selbst.
Hannele

Antworten:

985

Ich bin mir nicht sicher, was Sie versuchen zu tun. Sie können eine do-while-Schleife wie folgt implementieren:

while True:
  stuff()
  if fail_condition:
    break

Oder:

stuff()
while not fail_condition:
  stuff()

Was machst du, wenn du versuchst, eine do while-Schleife zu verwenden, um das Zeug in der Liste zu drucken? Warum nicht einfach verwenden:

for i in l:
  print i
print "done"

Aktualisieren:

Haben Sie eine Liste mit Zeilen? Und Sie möchten es weiter durchlaufen? Wie wäre es mit:

for s in l: 
  while True: 
    stuff() 
    # use a "break" instead of s = i.next()

Scheint das etwas in der Nähe von dem zu sein, was Sie wollen würden? Mit Ihrem Codebeispiel wäre es:

for s in some_list:
  while True:
    if state is STATE_CODE:
      if "//" in s:
        tokens.add( TOKEN_COMMENT, s.split( "//" )[1] )
        state = STATE_COMMENT
      else :
        tokens.add( TOKEN_CODE, s )
    if state is STATE_COMMENT:
      if "//" in s:
        tokens.append( TOKEN_COMMENT, s.split( "//" )[1] )
        break # get next s
      else:
        state = STATE_CODE
        # re-evaluate same line
        # continues automatically
Tom
quelle
1
Ich muss eine Zustandsmaschine erstellen. In der Zustandsmaschine ist es ein normaler Fall, die CURRENT-Anweisung neu zu bewerten, daher muss ich fortfahren, ohne das nächste Element zu wiederholen. Ich weiß nicht, wie ich so etwas in 'for s in l:' iteration :( machen soll. In der do-while-Schleife bewertet 'continue' das aktuelle Element neu, Iteration am Ende
grigoryvp
Meinen Sie damit, dass Sie Ihren Platz in der Liste im Auge behalten müssen? Auf diese Weise können Sie, wenn Sie denselben Zustand zurückgeben, dort weitermachen, wo Sie aufgehört haben? Geben Sie etwas mehr Kontext. Es scheint, als wären Sie besser dran, einen Index in die Liste aufzunehmen.
Tom
Danke, ich habe Ihren Pseudocode kommentiert ... Ihr Beispiel scheint irgendwie schlecht zu sein, da Sie "//" anscheinend genauso behandeln, egal in welchem ​​Zustand Sie sich befinden. Ist dies auch ein echter Code, in dem Sie Kommentare verarbeiten? Was ist, wenn Sie Zeichenfolgen mit Schrägstrichen haben? dh: print "blah // <- bringt dich das durcheinander?"
Tom
4
Es ist eine Schande, dass Python keine Do-While-Schleife hat. Python ist trocken, oder?
Kr0e
43
Siehe auch PEP 315 für die offizielle Haltung / Begründung: "Benutzern der Sprache wird empfohlen, das while-True-Formular mit einem inneren if-break zu verwenden, wenn eine do-while-Schleife angemessen gewesen wäre."
dtk
311

Hier ist eine sehr einfache Möglichkeit, eine Do-While-Schleife zu emulieren:

condition = True
while condition:
    # loop body here
    condition = test_loop_condition()
# end of loop

Die Hauptmerkmale einer do-while-Schleife sind, dass der Schleifenkörper immer mindestens einmal ausgeführt wird und dass die Bedingung am unteren Rand des Schleifenkörpers ausgewertet wird. Die hier gezeigte Kontrollstruktur führt beide aus, ohne dass Ausnahmen oder break-Anweisungen erforderlich sind. Es wird eine zusätzliche boolesche Variable eingeführt.

Pulverflasche
quelle
11
Es wird nicht immer eine zusätzliche boolesche Variable hinzugefügt. Oft gibt es bereits etwas, dessen Zustand getestet werden kann.
Martineau
14
Der Grund, warum mir diese Lösung am besten gefällt, ist, dass sie keine weitere Bedingung hinzufügt, sondern nur einen Zyklus umfasst. Wenn Sie einen guten Namen für die Hilfsvariable auswählen, ist die gesamte Struktur ziemlich klar.
Roberto
4
HINWEIS: Obwohl dies die ursprüngliche Frage beantwortet, ist dieser Ansatz weniger flexibel als die Verwendung break. Insbesondere wenn nachher Logik benötigt test_loop_condition()wird, die nicht ausgeführt werden sollte, wenn wir fertig sind, muss sie eingewickelt werden if condition:. Übrigens conditionist vage. Beschreibender: moreoder notDone.
ToolmakerSteve
7
@ ToolmakerSteve Ich bin anderer Meinung. Ich verwende es selten breakin Schleifen, und wenn ich es in Code finde, den ich pflege, stelle ich fest, dass die Schleife meistens ohne sie geschrieben worden sein könnte. Die vorgestellte Lösung ist IMO der klarste Weg, ein do while-Konstrukt in Python darzustellen.
unsinnig
1
Im Idealfall wird die Bedingung als etwas Beschreibendes bezeichnet, wie has_no_errorsoder end_reached(in diesem Fall würde die Schleife beginnenwhile not end_reached
Josiah Yoder
75

Mein Code unten könnte eine nützliche Implementierung sein, die den Hauptunterschied zwischen hervorhebt vs. so wie ich es verstehe.

In diesem Fall durchlaufen Sie die Schleife also immer mindestens einmal.

first_pass = True
while first_pass or condition:
    first_pass = False
    do_stuff()
evan54
quelle
2
Richtige Antwort, würde ich argumentieren. Außerdem wird ein Bruch vermieden , um eine sichere Verwendung in Try / Except-Blöcken zu gewährleisten.
Zv_oDD
Vermeidet der JIT / Optimierer das erneute Testen von first_pass nach dem ersten Durchgang? andernfalls wäre es ein
nerviges
2
@markhahn das ist wirklich klein, aber wenn Sie sich um solche Details kümmern, können Sie die 2 Booleschen Werte in der Schleife umkehren : while condition or first_pass:. Dann conditionwird immer zuerst und insgesamt first_passnur zweimal ausgewertet (erste und letzte Iteration). Vergessen Sie nicht, conditionvor der Schleife zu initialisieren , was Sie wollen.
pLOPeGG
HM, interessant, ich hatte tatsächlich absichtlich den umgekehrten Weg gewählt, um den Zustand nicht initialisieren zu müssen und daher nur minimale Änderungen am Code zu erfordern. Das heißt, ich
verstehe
33

Eine Ausnahme unterbricht die Schleife, sodass Sie sie auch außerhalb der Schleife behandeln können.

try:
  while True:
    if s:
      print s
    s = i.next()
except StopIteration:   
  pass

Ich denke, dass das Problem mit Ihrem Code darin besteht, dass das Verhalten von breakinnen exceptnicht definiert ist. Im Allgemeinen breakgeht es nur eine Ebene nach oben, also geht zB breakinnen trydirekt zu finally(wenn es existiert) einem aus der try, aber nicht aus der Schleife.

Verwandte PEP: http://www.python.org/dev/peps/pep-3136
Verwandte Frage: Ausbrechen verschachtelter Schleifen

vartec
quelle
8
Es ist jedoch empfehlenswert, nur in der try-Anweisung anzugeben, was Sie erwarten, um Ihre Ausnahme auszulösen, damit Sie keine unerwünschten Ausnahmen abfangen.
Paggas
7
@PiPeep: RTFM, Suche nach EAFP.
Vartec
2
@PiPeep: Kein Problem, denken Sie daran, dass das, was für einige Sprachen gilt, für andere möglicherweise nicht gilt. Python ist für die intensive Verwendung von Ausnahmen optimiert.
Vartec
5
break and continue sind in jeder Klausel einer try / exception / finally-Anweisung genau definiert. Sie ignorieren sie einfach und brechen entweder aus oder fahren mit der nächsten Iteration der enthaltenden while- oder for-Schleife fort. Als Komponenten der Schleifenkonstrukte sind sie nur für while- und for-Anweisungen relevant und lösen einen Syntaxfehler aus, wenn sie vor Erreichen der innersten Schleife auf eine Klassen- oder def-Anweisung stoßen. Sie ignorieren if-, with- und try-Anweisungen.
Ncoghlan
1
.. was ein wichtiger Fall ist
Javadba
33
do {
  stuff()
} while (condition())

->

while True:
  stuff()
  if not condition():
    break

Sie können eine Funktion ausführen:

def do_while(stuff, condition):
  while condition(stuff()):
    pass

Aber 1) Es ist hässlich. 2) Bedingung sollte eine Funktion mit einem Parameter sein, die mit Dingen gefüllt sein soll (dies ist der einzige Grund , die klassische while-Schleife nicht zu verwenden.)

ZeD
quelle
5
Schreiben while True: stuff(); if not condition(): breakist eine sehr gute Idee. Vielen Dank!
Noctis Skytower
2
@ZeD, warum ist 1) hässlich? Es ist ganz in Ordnung, IMHO
Sergey Lossev
@SergeyLossev Es wird schwierig sein, die Logik des Programms zu verstehen, da es zunächst als Endlosschleife erscheint, wenn Sie viel 'Zeug'-Code dazwischen haben.
Exic
16

Hier ist eine verrücktere Lösung eines anderen Musters - unter Verwendung von Coroutinen. Der Code ist immer noch sehr ähnlich, aber mit einem wichtigen Unterschied; Es gibt überhaupt keine Ausstiegsbedingungen! Die Coroutine (Kette von Coroutinen wirklich) stoppt nur, wenn Sie aufhören, sie mit Daten zu füttern.

def coroutine(func):
    """Coroutine decorator

    Coroutines must be started, advanced to their first "yield" point,
    and this decorator does this automatically.
    """
    def startcr(*ar, **kw):
        cr = func(*ar, **kw)
        cr.next()
        return cr
    return startcr

@coroutine
def collector(storage):
    """Act as "sink" and collect all sent in @storage"""
    while True:
        storage.append((yield))

@coroutine      
def state_machine(sink):
    """ .send() new parts to be tokenized by the state machine,
    tokens are passed on to @sink
    """ 
    s = ""
    state = STATE_CODE
    while True: 
        if state is STATE_CODE :
            if "//" in s :
                sink.send((TOKEN_COMMENT, s.split( "//" )[1] ))
                state = STATE_COMMENT
            else :
                sink.send(( TOKEN_CODE, s ))
        if state is STATE_COMMENT :
            if "//" in s :
                sink.send(( TOKEN_COMMENT, s.split( "//" )[1] ))
            else
                state = STATE_CODE
                # re-evaluate same line
                continue
        s = (yield)

tokens = []
sm = state_machine(collector(tokens))
for piece in i:
    sm.send(piece)

Der obige Code sammelt alle Token als Tupel tokensund ich gehe davon aus, dass es keinen Unterschied zwischen .append()und .add()im Originalcode gibt.

u0b34a0f6ae
quelle
4
Wie würden Sie dies heute in Python 3.x schreiben?
Noctis Skytower
14

Ich habe das so gemacht ...

condition = True
while condition:
     do_stuff()
     condition = (<something that evaluates to True or False>)

Dies scheint mir die vereinfachte Lösung zu sein. Ich bin überrascht, dass ich sie hier noch nicht gesehen habe. Dies kann natürlich auch umgekehrt werden

while not condition:

usw.

Gareth Lock
quelle
Sie sagen "Ich bin überrascht, dass ich es hier noch nicht gesehen habe" - aber ich sehe keinen Unterschied zu beispielsweise der Pulverflaschenlösung von 2010. Es ist genau das gleiche. ("Bedingung = Wahr während Bedingung: # Schleifenkörper hier Bedingung = test_loop_condition () # Ende der Schleife")
cslotty
10

für eine do - while - Schleife mit try - Anweisungen

loop = True
while loop:
    generic_stuff()
    try:
        questionable_stuff()
#       to break from successful completion
#       loop = False  
    except:
        optional_stuff()
#       to break from unsuccessful completion - 
#       the case referenced in the OP's question
        loop = False
   finally:
        more_generic_stuff()

alternativ, wenn die 'finally'-Klausel nicht erforderlich ist

while True:
    generic_stuff()
    try:
        questionable_stuff()
#       to break from successful completion
#       break  
    except:
        optional_stuff()
#       to break from unsuccessful completion - 
#       the case referenced in the OP's question
        break
Kennzeichen
quelle
7
while condition is True: 
  stuff()
else:
  stuff()
MuSheng
quelle
8
Ew. Das scheint deutlich hässlicher als eine Pause.
Mattdm
5
Das ist klug, aber es muss stuffeine Funktion sein oder der Codekörper muss wiederholt werden.
Noctis Skytower
12
Alles was benötigt wird ist, while condition:weil is Trueimpliziert ist.
Martineau
2
Dies schlägt fehl, wenn dies conditionvon einer inneren Variablen von abhängt stuff(), da diese Variable zu diesem Zeitpunkt nicht definiert ist.
yo
5
Nicht die gleiche Logik, da bei der letzten Iteration bei Bedingung! = True: Der Code wird ein letztes Mal aufgerufen. Wo , wie ein Do While ruft den Code einmal zuerst, dann prüft Zustand vor Wieder läuft. Do While: Block einmal ausführen; Überprüfen Sie dann diese Antwort und führen Sie sie erneut aus : Überprüfen Sie sie und führen Sie sie erneut aus. Führen Sie dann den Codeblock einmal aus . Großer Unterschied!
Zv_oDD
7

Schneller Hack:

def dowhile(func = None, condition = None):
    if not func or not condition:
        return
    else:
        func()
        while condition():
            func()

Verwenden Sie wie folgt:

>>> x = 10
>>> def f():
...     global x
...     x = x - 1
>>> def c():
        global x
        return x > 0
>>> dowhile(f, c)
>>> print x
0
Naftuli Kay
quelle
3

Warum tust du es nicht einfach?

for s in l :
    print s
print "done"

?

Martin
quelle
1
Ich muss eine Zustandsmaschine erstellen. In der Zustandsmaschine ist es ein normaler Fall, die CURRENT-Anweisung neu zu bewerten, daher muss ich fortfahren, ohne das nächste Element zu wiederholen. Ich weiß nicht, wie ich so etwas in 'for s in l:' iteration :( machen soll. In der do-while-Schleife bewertet 'continue' das aktuelle Element neu, die Iteration am Ende.
grigoryvp
Können Sie dann einen Pseudocode für Ihre Zustandsmaschine definieren, damit wir Sie auf die beste pythonische Lösung hinweisen können? Ich weiß nicht viel über Zustandsautomaten (und bin wahrscheinlich nicht der einzige). Wenn Sie uns also etwas über Ihren Algorithmus erzählen, können wir Ihnen leichter helfen.
Martin
For-Schleife funktioniert nicht für Dinge wie: a = fun (), während a == 'zxc': sleep (10) a = fun ()
harry
Dies verfehlt völlig den Punkt der Überprüfung einer booleschen Bedingung
Javadba
1

Sehen Sie, ob dies hilft:

Setzen Sie ein Flag im Exception-Handler und überprüfen Sie es, bevor Sie am s arbeiten.

flagBreak = false;
while True :

    if flagBreak : break

    if s :
        print s
    try :
        s = i.next()
    except StopIteration :
        flagBreak = true

print "done"
Nrj
quelle
3
Könnte durch Verwenden while not flagBreak:und Entfernen der vereinfacht werden if (flagBreak) : break.
Martineau
1
Ich vermeide Variablen mit dem Namen - flagIch kann nicht ableiten, was ein wahrer Wert oder ein falscher Wert bedeutet. Verwenden Sie stattdessen doneoder endOfIteration. Der Code wird zu while not done: ....
IceArdor
1

Wenn Sie sich in einem Szenario befinden, in dem Sie eine Schleife ausführen, während eine Ressource nicht verfügbar ist, oder etwas Ähnliches, das eine Ausnahme auslöst, können Sie Folgendes verwenden

import time

while True:
    try:
       f = open('some/path', 'r')
    except IOError:
       print('File could not be read. Retrying in 5 seconds')   
       time.sleep(5)
    else:
       break
Ajit
quelle
0

Für mich ist eine typische while-Schleife ungefähr so:

xBool = True
# A counter to force a condition (eg. yCount = some integer value)

while xBool:
    # set up the condition (eg. if yCount > 0):
        (Do something)
        yCount = yCount - 1
    else:
        # (condition is not met, set xBool False)
        xBool = False

Ich könnte auch eine for..- Schleife in die while-Schleife aufnehmen, wenn die Situation dies erfordert, um eine andere Bedingung zu durchlaufen.

user12379095
quelle