Emulare un ciclo do-while in Python?


799

Devo emulare un ciclo do-while in un programma Python. Sfortunatamente, il seguente codice semplice non funziona:

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"

Invece di "1,2,3, done", stampa il seguente output:

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

Cosa posso fare per rilevare l'eccezione "stop iteration" e interrompere correttamente un ciclo while?

Un esempio del perché una cosa del genere può essere necessaria è mostrato sotto come pseudocodice.

Macchina a stati:

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

4
Um ... Non è un vero "do-while"; è semplicemente un "da fare per sempre". Cosa c'è di sbagliato in "while True" e "break"?
S.Lott

70
S. Lott: Sono abbastanza sicuro che la sua domanda fosse su come implementare do mentre in Python. Quindi, non mi aspetto che il suo codice sia completamente corretto. Inoltre, è molto vicino a un po '... sta verificando una condizione alla fine del ciclo "per sempre" per vedere se dovrebbe scoppiare. Non è "fare per sempre".
Tom,

4
quindi ... il tuo codice di esempio iniziale in realtà funziona per me senza problemi e non ottengo quel traceback. è un idioma appropriato per un ciclo do while in cui la condizione di rottura è l'esaurimento dell'iteratore. in genere, dovresti impostare s=i.next()piuttosto che Nessuno e possibilmente fare qualche lavoro iniziale piuttosto che rendere inutile il tuo primo passaggio attraverso il ciclo.
underrun,

3
@underrun Sfortunatamente, il post non è taggato con la versione di Python in uso - lo snippet originale funziona anche per me usando 2.7, presumibilmente a causa degli aggiornamenti del linguaggio Python stesso.
Hannele,

Risposte:


985

Non sono sicuro di quello che stai cercando di fare. È possibile implementare un ciclo do-while in questo modo:

while True:
  stuff()
  if fail_condition:
    break

O:

stuff()
while not fail_condition:
  stuff()

Cosa stai facendo cercando di usare un ciclo do while per stampare le cose nell'elenco? Perché non usare solo:

for i in l:
  print i
print "done"

Aggiornare:

Quindi hai un elenco di righe? E vuoi continuare a iterarlo? Che ne dite di:

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

Ti sembra qualcosa di simile a quello che vorresti? Con il tuo esempio di codice, sarebbe:

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

1
ho bisogno di creare una macchina a stati. Nella macchina a stati è normale rivalutare l'istruzione CURRENT, quindi devo "continuare" senza ripetere l'elemento successivo. Non so come fare una cosa del genere in 'for s in l:' iteration :(. Nel ciclo do-while, 'continue' rivaluterà l'elemento corrente, iterazione alla fine
grigoryvp

Vuoi dire che devi tenere traccia del tuo posto nell'elenco? In questo modo quando ritorni allo stesso stato, puoi riprendere da dove eri rimasto? Dai un po 'più di contesto. Sembra che potresti stare meglio usando un indice nell'elenco.
Tom,

Grazie, ho commentato il tuo pseudocodice ... il tuo esempio sembra un po 'cattivo dato che sembri gestire "//" allo stesso modo, indipendentemente dallo stato in cui ti trovi. Inoltre, questo vero codice è dove stai elaborando i commenti? Cosa succede se si hanno stringhe con barre? vale a dire: stampa "blah // <- ti dà fastidio?"
Tom,

4
È un peccato che Python non abbia un ciclo do-while. Python è ASCIUTTO, eh?
Kr0e

43
Vedi anche PEP 315 per la posizione / giustificazione ufficiale: "Si consiglia agli utenti della lingua di usare la forma while-True con un if-break interno quando un ciclo do-while sarebbe stato appropriato."
dtk,

311

Ecco un modo molto semplice per emulare un ciclo do-while:

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

Le caratteristiche principali di un ciclo do-while sono che il corpo del ciclo viene sempre eseguito almeno una volta e che la condizione viene valutata nella parte inferiore del corpo del ciclo. La struttura di controllo mostrata qui realizza entrambe queste cose senza la necessità di eccezioni o dichiarazioni di rottura. Introduce una variabile booleana aggiuntiva.


11
Non aggiunge sempre una variabile booleana aggiuntiva. Spesso ci sono già qualcosa che esiste già il cui stato può essere testato.
martineau,

14
Il motivo per cui mi piace di più questa soluzione è che non aggiunge un'altra condizione, è ancora solo un ciclo e se si sceglie un buon nome per la variabile helper l'intera struttura è abbastanza chiara.
Roberto,

4
NOTA: sebbene questo affronti la domanda originale, questo approccio è meno flessibile dell'uso break. In particolare, se c'è la logica necessaria DOPO test_loop_condition(), che non dovrebbe essere eseguita una volta che abbiamo finito, deve essere racchiusa if condition:. A proposito, conditionè vago. Più descrittivo: moreo notDone.
ToolmakerSteve

7
@ToolmakerSteve Non sono d'accordo. Uso raramente breaknei loop e quando lo incontro nel codice che sostengo trovo che il loop, più spesso, avrebbe potuto essere scritto senza di esso. La soluzione presentata è, IMO, il modo più chiaro per rappresentare un costrutto do while in Python.
nonsensickle,

1
Idealmente, la condizione verrà denominata in modo descrittivo, come has_no_errorso end_reached(nel qual caso il ciclo inizierebbewhile not end_reached
Josiah Yoder,

75

Il mio codice di seguito potrebbe essere un'implementazione utile, evidenziando la differenza principale tra vs come ho capito.

Quindi, in questo caso, fai sempre il giro almeno una volta.

first_pass = True
while first_pass or condition:
    first_pass = False
    do_stuff()

2
Risposta corretta, direi. Inoltre evita rotture , per un utilizzo sicuro in blocchi try / tranne.
Zv_oDD,

il jit / ottimizzatore evita di ripetere il test first_pass dopo il primo passaggio? altrimenti, sarebbe un fastidioso, anche se forse minore, problema di performance
markhahn,

2
@markhahn questo è veramente minore, ma se si cura di questi dettagli, è possibile intervert i 2 booleani nel ciclo: while condition or first_pass:. Quindi conditionviene sempre valutato per primo e nel complesso first_passviene valutato solo due volte (prima e ultima iterazione). Non dimenticare di inizializzare conditionprima del loop quello che vuoi.
pLOPeGG

HM, interessante che in realtà avevo scelto il contrario per non dover inizializzare la condizione e quindi richiedere modifiche minime al codice. Detto questo, vedo il tuo punto
evan54,

33

L'eccezione interromperà il ciclo, quindi potresti anche gestirlo al di fuori del ciclo.

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

Immagino che il problema con il tuo codice sia che il comportamento di breakinside exceptnon è definito. Di solito breaksale solo di un livello, quindi ad esempio breakinside tryva direttamente finally(se esiste) fuori da try, ma non fuori dal circuito.

PEP correlato: http://www.python.org/dev/peps/pep-3136
Domanda correlata: eliminazione di loop nidificati


8
È buona norma, tuttavia, avere solo all'interno dell'istruzione try ciò che si prevede di generare un'eccezione, per evitare di rilevare eccezioni indesiderate.
Paggas,

7
@PiPeep: RTFM, ricerca EAFP.
vartec,

2
@PiPeep: nessun problema, tieni presente che ciò che è vero per alcune lingue, potrebbe non essere vero per altre. Python è ottimizzato per un uso intensivo delle eccezioni.
vartec,

5
break and continue sono perfettamente definiti in qualsiasi clausola di un'istruzione try / tranne / finally. Semplicemente li ignorano e si interrompono o passano alla successiva iterazione del ciclo while o for appropriato. Come componenti dei costrutti del ciclo, sono rilevanti solo per le istruzioni while e for e attivano un errore di sintassi se si imbattono in un'istruzione di classe o def prima di raggiungere il ciclo più interno. Ignorano le dichiarazioni if, with e try.
ncoghlan,

1
.. che è un caso importante
javadba

33
do {
  stuff()
} while (condition())

->

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

Puoi fare una funzione:

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

Ma 1) È brutto. 2) La condizione dovrebbe essere una funzione con un parametro, che dovrebbe essere riempita da cose (è l'unica ragione per non usare il classico ciclo while).


5
Scrivere while True: stuff(); if not condition(): breakè un'ottima idea. Grazie!
Noctis Skytower,

2
@ZeD, perché 1) è brutto? Va tutto bene, IMHO
Sergey Lossev il

@SergeyLossev Sarà difficile cogliere la logica del programma perché all'inizio appare come un ciclo infinito, se hai un sacco di codice "roba" in mezzo.
exic

16

Ecco una soluzione più folle di un modello diverso - usando le coroutine. Il codice è ancora molto simile, ma con un'importante differenza; non ci sono condizioni di uscita! La coroutine (catena di coroutine in realtà) si interrompe quando si interrompe l'alimentazione con i dati.

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)

Il codice sopra raccoglie tutti i token come tuple tokense suppongo che non ci siano differenze tra .append()e .add()nel codice originale.


4
Come lo scriveresti oggi in Python 3.x?
Noctis Skytower,

14

Il modo in cui l'ho fatto è il seguente ...

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

Questa mi sembra la soluzione semplicistica, sono sorpreso di non averlo già visto qui. Questo ovviamente può anche essere invertito

while not condition:

eccetera.


Dici "Sono sorpreso di non averlo già visto qui", ma non vedo alcuna differenza, diciamo, dalla soluzione di powderflask del 2010. È esattamente la stessa. ("condition = True while condition: # loop body here condition = test_loop_condition () # end of loop")
cslotty

10

per un ciclo do - while contenente istruzioni try

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

in alternativa, quando non è necessaria la clausola "finally"

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

7
while condition is True: 
  stuff()
else:
  stuff()

8
Ew. Sembra significativamente più brutto che usare una pausa.
Mattdm,

5
È intelligente, ma richiede stuffdi essere una funzione o di ripetere il corpo del codice.
Noctis Skytower,

12
Tutto ciò che serve è while condition:perché is Trueè implicito.
martineau,

2
questo fallisce se conditiondipende da qualche variabile interna di stuff(), perché quella variabile non è definita in quel momento.
giovedì

5
Non è la stessa logica, perché nell'ultima iterazione quando condition! = True: chiama il codice un'ultima volta. Where as Do While , chiama prima il codice una volta, quindi controlla le condizioni prima di rieseguire. Do While: esegue il blocco una volta; quindi controllare e rieseguire , questa risposta: controllare e rieseguire; quindi eseguire il blocco di codice una volta . Grande differenza!
Zv_oDD,

7

Hack rapido:

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

Usa così:

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

3

Perché non lo fai e basta

for s in l :
    print s
print "done"

?


1
ho bisogno di creare una macchina a stati. Nella macchina a stati è normale rivalutare l'istruzione CURRENT, quindi devo "continuare" senza ripetere l'elemento successivo. Non so come fare una cosa simile in 'for s in l:' iteration :(. Nel ciclo do-while, 'continue' rivaluterà l'elemento corrente, iterazione alla fine.
grigoryvp

allora, puoi definire alcuni pseudo-codice per la tua macchina a stati, così possiamo suggerirti la migliore soluzione pythonic? Non so molto sulle macchine a stati (e probabilmente non sono l'unico), quindi se ci parlate un po 'del vostro algoritmo, sarà più facile per noi aiutarvi.
Martin,

For loop non funziona per cose come: a = fun () mentre a == 'zxc': sleep (10) a = fun ()
harry

Questo manca completamente al punto di controllare una condizione booleana
javadba,

1

Vedi se questo aiuta:

Imposta un flag all'interno del gestore eccezioni e controllalo prima di lavorare su s.

flagBreak = false;
while True :

    if flagBreak : break

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

print "done"

3
Potrebbe essere semplificato usando while not flagBreak:e rimuovendo il file if (flagBreak) : break.
martineau,

1
Evito le variabili denominate flag- Non riesco a dedurre il significato di un valore Vero o Falso. Utilizzare invece doneo endOfIteration. Il codice si trasforma in while not done: ....
IceArdor,

1

Se ti trovi in ​​uno scenario in cui esegui il loop mentre una risorsa non è disponibile o qualcosa di simile che genera un'eccezione, puoi utilizzare qualcosa di simile

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

0

Per me un tipico ciclo while sarà qualcosa del genere:

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

Potrei includere un for..loop anche nel ciclo while, se la situazione lo giustifica, per passare in rassegna un altro insieme di condizioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.