Cum să emulați o buclă do-while? (Programare, Python, Bucla While, Face În Timp Ce)

grigoryvp a intrebat.

Am nevoie să emulez o buclă do-while într-un program Python. Din păcate, următorul cod simplu nu funcționează:

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"

În loc de „1,2,3,done”, se tipărește următoarea ieșire:

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

Ce pot face pentru a prinde excepția „stop iterație” și pentru a întrerupe corect o buclă whileloop?

Un exemplu de ce ar putea fi necesar un astfel de lucru este prezentat mai jos sub formă de pseudocod.

Mașina de stare:

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

Comentarii

  • Ăăă… Nu este un „do-while” propriu-zis; este pur și simplu un „do-forever”. Ce este în neregulă cu „while True” și „break”? –  > Por S.Lott.
  • 82

  • S. Lott: Sunt destul de sigur că întrebarea lui era despre cum să implementeze „do while” în python. Așadar, nu m-aș aștepta ca codul său să fie complet corect. De asemenea, el este foarte aproape de un „do while”… verifică o condiție la sfârșitul buclei „forever” pentru a vedea dacă ar trebui să iasă din el. Nu este „do-forever”. –  > Por Tom.
  • Deci… codul dvs. de exemplu inițial funcționează pentru mine fără probleme și nu primesc acel traceback. acesta este un idiom adecvat pentru o buclă „do while” în care condiția de întrerupere este epuizarea iteratorului. în mod normal, ați seta s=i.next() în loc de None și, eventual, să efectuați o muncă inițială, în loc să faceți inutilă prima trecere prin buclă. –  > Por underrun.
  • @underrun Din păcate, postarea nu este etichetată cu ce versiune de Python a fost folosită – fragmentul original funcționează și pentru mine folosind 2.7, probabil din cauza actualizărilor limbajului Python în sine. –  > Por Hannele.
17 răspunsuri
Tom

Nu sunt sigur de ceea ce încercați să faceți. Puteți implementa o buclă do-while astfel:

while True:
  stuff()
  if fail_condition:
    break

Sau:

stuff()
while not fail_condition:
  stuff()

Ce faci încercând să folosești o buclă do while pentru a imprima lucrurile din listă? De ce nu folosiți doar:

for i in l:
  print i
print "done"

Update:

Deci ai o listă de linii? Și vrei să continui să o parcurgi? Ce zici de:

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

Vi se pare ceva apropiat de ceea ce doriți? Cu exemplul tău de cod, ar fi:

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

Comentarii

  • Trebuie să creez o mașină de stare. În mașina de stare este un caz normal de reevaluare a instrucțiunii CURRENT, deci trebuie să „continui” fără a itera următorul element. Nu știu cum să fac un astfel de lucru în iterația ‘for s in l:’ :(. În bucla do-while, ‘continue’ va reevalua elementul curent, iterația la sfârșit –  > Por grigoryvp.
  • Vrei să spui că trebuie să ții evidența locului tău în listă? În acest fel, atunci când vă întoarceți în aceeași stare, puteți relua de unde ați rămas? Dați un pic mai mult context. Se pare că ar fi mai bine să folosiți un index în listă. –  > Por Tom.
  • Mulțumesc, am comentat pseudocodul tău… exemplul tău pare cam prost, deoarece pari să tratezi „//” în același mod, indiferent de starea în care te afli. De asemenea, acesta este codul real în care procesați comentariile? Ce se întâmplă dacă ai șiruri de caractere cu slash-uri? adică: print „blah // <– te încurcă asta?” –  > Por Tom.
  • Este păcat că python nu are o buclă do-while. Python este DRY, nu-i așa ? –  > Por Kr0e.
  • 65

  • Vezi și PEP 315 pentru poziția/justificarea oficială: „Utilizatorii limbajului sunt sfătuiți să utilizeze forma while-True cu o întrerupere internă if-break atunci când o buclă do-while ar fi fost adecvată.” –  > Por dtk.
powderflask

Iată o modalitate foarte simplă de a emula o buclă do-while:

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

Caracteristicile cheie ale unei bucle do-while sunt faptul că corpul buclei se execută întotdeauna cel puțin o dată și că condiția este evaluată în partea de jos a corpului buclei. Structura de control prezentată aici îndeplinește ambele condiții fără a fi nevoie de excepții sau instrucțiuni break. Aceasta introduce o variabilă booleană suplimentară.

Comentarii

  • Nu adaugă întotdeauna o variabilă booleană suplimentară. De multe ori există ceva(e) care există deja și a căror stare poate fi testată. –  > Por martineau.
  • 20

  • Motivul pentru care îmi place cel mai mult această soluție este că nu adaugă o altă condiție, este tot un singur ciclu, iar dacă alegeți un nume bun pentru variabila ajutătoare, întreaga structură este destul de clară. –  > Por Roberto.
  • NOTĂ: Deși aceasta răspunde la întrebarea inițială, această abordare este mai puțin flexibilă decât utilizarea break. Mai exact, dacă este nevoie de o logică DUPĂ test_loop_condition(), , care nu trebuie executată după ce am terminat, trebuie să fie inclusă în if condition:. BTW, condition este vag. Mai descriptivă: more sau notDone. –  > Por ToolmakerSteve.
  • @ToolmakerSteve Nu sunt de acord. Eu folosesc rar break în bucle, iar atunci când îl întâlnesc în codul pe care îl întrețin, constat că bucla, de cele mai multe ori, ar fi putut fi scrisă fără el. Soluția prezentată este, IMO, cea mai bună cea mai clară mod de a reprezenta o construcție do while în python. –  > Por nonsensickle.
  • În mod ideal, condiția se va numi ceva descriptiv, cum ar fi has_no_errors sau end_reached (caz în care bucla ar începe while not end_reached –  > Por Josiah Yoder.
evan54

Codul meu de mai jos ar putea fi o implementare utilă, evidențiind principala diferență dintre față de așa cum o înțeleg eu.

Deci, în acest caz, treceți întotdeauna prin buclă cel puțin o dată.

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

Comentarii

  • Răspunsul corect, aș spune eu. În plus, se evită break, , pentru o utilizare sigură în blocurile try/except. –  > Por Zv_oDD.
  • evită jit/optimizatorul jit/optimizator să retesteze first_pass după prima trecere? altfel, ar fi o problemă de performanță enervantă, deși poate minoră, –  > Por markhahn.
  • @markhahn acest lucru este cu adevărat minor, dar dacă vă pasă de astfel de detalii, puteți inversa cele 2 booleane în buclă: while condition or first_pass:. Apoi conditioneste întotdeauna evaluată prima și, în general first_pass este evaluat doar de două ori (prima și ultima iterație). Nu uitați să inițializați condition înainte de buclă la valoarea pe care o doriți. –  > Por pLOPeGG.
  • HM, interesant, eu de fapt alesesem invers intenționat pentru a nu fi nevoit să inițializez condiția și astfel să fie nevoie de modificări minime în cod. Acestea fiind spuse, înțeleg ce vrei să spui.  > Por evan54.
  • @AkhilNambiar Nu există nicio problemă cu asta? Nu este prima trecere… după prima trecere. –  > Por Simply Beautiful Art.
ZeD
do {
  stuff()
} while (condition())

->

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

Puteți face o funcție:

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

Dar1) Este urât.2) Condiția ar trebui să fie o funcție cu un singur parametru, care ar trebui să fie completată de chestii (este singurul motiv pentru care nu pentru a folosi clasica buclă while).

Comentarii

  • Scrierea while True: stuff(); if not condition(): break este o idee foarte bună. Mulțumesc! –  > Por Noctis Skytower.
  • @ZeD, de ce este 1) urât? Este destul de ok, IMHO –  > Por Serghei Lossev.
  • @SergeyLossev Va fi dificil să înțelegeți logica programului, deoarece la început apare ca o buclă infinită, dacă aveți o mulțime de coduri „chestii” între ele. –  > Por exic.
vartec

Excepția va întrerupe bucla, așa că ar fi bine să o tratezi în afara buclei.

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

Cred că problema cu codul dvs. este că comportamentul de break în interiorul except nu este definit. În general, break merge doar un singur nivel mai sus, astfel încât, de exemplu. break inside try merge direct la finally (în cazul în care există) și o ieșire din try, , dar nu și în afara buclei.

PEP conex: http://www.python.org/dev/peps/pep-3136
Întrebare conexă: Ieșirea din buclele imbricate

Comentarii

  • Totuși, este o bună practică să aveți în interiorul declarației try doar ceea ce vă așteptați să arunce excepția, pentru a nu prinde excepții nedorite. –  > Por Paggas.
  • @PiPeep: RTFM, căutați EAFP. –  > Por vartec.
  • @PiPeep: nici o problemă, doar țineți cont de faptul că ceea ce este valabil pentru unele limbi, poate să nu fie valabil pentru altele. Python este optimizat pentru utilizarea intensivă a excepțiilor. –  > Por vartec.
  • break și continue sunt perfect bine definite în orice clauză a unei instrucțiuni try/except/finally. Pur și simplu le ignoră și fie ies din sau trec la următoarea iterație a buclei while sau for care le conține, după caz. În calitate de componente ale construcțiilor de buclă, acestea sunt relevante doar pentru instrucțiunile while și for și declanșează o eroare de sintaxă dacă se întâlnesc cu o declarație class sau def înainte de a ajunge la cea mai interioară buclă. Ele ignoră instrucțiunile if, with și try. –  > Por ncoghlan.
  • … care este un caz important –  > Por StephenBoesch.
u0b34a0f6ae

Iată o soluție mai nebună de un model diferit – folosind corutine. Codul este încă foarte asemănător, dar cu o diferență importantă; nu există deloc condiții de ieșire! Corutina (de fapt, lanțul de corutine) se oprește pur și simplu atunci când nu mai este alimentată cu date.

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)

Codul de mai sus colectează toate jetoanele ca tupluri în tokens și presupun că nu există nicio diferență între .append() și .add() în codul original.

Comentarii

  • Cum ați scrie acest lucru în Python 3.x astăzi? –  > Por Noctis Skytower.
Gareth Lock

Modul în care am făcut acest lucru este următorul…

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

Aceasta mi se pare a fi soluția simplistă, mă mir că nu am văzut-o deja aici. În mod evident, această soluție poate fi inversată și pentru

while not condition:

etc.

Comentarii

  • Spui „Sunt surprins că nu am văzut-o deja aici” – dar nu văd nicio diferență față de, să zicem, soluția lui powderflask din 2010. Este exact la fel. („condition = True while condition: # corpul buclei aici condiție = test_loop_condition() # sfârșitul buclei”) -) –  > Por cslotty.
Mark

pentru o buclă do – while care conține instrucțiuni 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()

alternativ, atunci când nu este nevoie de clauza „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

Jonathan Shemer

Python 3.8 are răspunsul.

Se numește expresii de atribuire. de la documentație:

# Loop over fixed length blocks
while (block := f.read(256)) != '':
    process(block)

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

Comentarii

  • Ew. Asta pare semnificativ mai urât decât folosirea unei pauze. –  > Por mattdm.
  • Asta este inteligent, dar necesită stuff să fie o funcție sau să se repete corpul de cod. –  > Por Noctis Skytower.
  • Tot ceea ce este necesar este while condition: pentru că is True este implicită. –  > Por martineau.
  • acest lucru eșuează dacă condition depinde de o variabilă interioară a lui stuff(), , deoarece acea variabilă nu este definită în acel moment. –  > Por yo’.
  • Nu este aceeași logică, pentru că la ultima iterație, când condiția != Adevărat : Se apelează codul o ultimă dată. În timp ce ca o Do While, , apelează codul o singură dată, apoi verifică condiția înainte de a o rula din nou. Do While : execută blocul o singură dată; apoi verifică și rulează din nou, , acest răspuns: verifică și rulează din nou; apoi execută o singură dată blocul de cod.. Mare diferență! –  > Por Zv_oDD.
Naftuli Kay

Hack rapid:

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

Folosiți așa:

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

Martin

De ce nu faci doar

for s in l :
    print s
print "done"

?

Comentarii

  • am nevoie să creez o mașină de stare. În mașina de stare este un caz normal să reevaluez instrucțiunea CURENT, deci trebuie să „continui” fără a itera următorul element. Nu știu cum să fac acest lucru în iterația ‘for s in l:’ :(. În bucla do-while, ‘continue’ va reevalua elementul curent, iterația la sfârșit. –  > Por grigoryvp.
  • atunci, poți să definești un pseudocod pentru mașina ta de stare, ca să te putem îndruma spre cea mai bună soluție pythonică ? Nu știu prea multe despre mașinile de stat(și probabil că nu sunt singurul), așa că dacă ne spui câte ceva despre algoritmul tău, ne va fi mai ușor să te ajutăm. –  > Por Martin.
  • For loop nu funcționează pentru lucruri de genul: a = fun() while a == ‘zxc’: sleep(10) a = fun() –  > Por harry.
  • Acest lucru ratează complet scopul verificării unei condiții booleene –  > Por StephenBoesch.
Nrj

Vedeți dacă acest lucru vă ajută :

Setați un indicator în interiorul gestionarului de excepții și verificați-l înainte de a lucra la s.

flagBreak = false;
while True :

    if flagBreak : break

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

print "done"

Comentarii

  • Ar putea fi simplificat prin utilizarea while not flagBreak: și eliminând if (flagBreak) : break. –  > Por martineau.
  • Eu evit variabilele cu numele flag–nu reușesc să deduc ce înseamnă o valoare Adevărat sau o valoare Fals. În schimb, utilizați done sau endOfIteration. Codul se transformă în while not done: .... –  > Por IceArdor.
Ajit

Dacă vă aflați într-un scenariu în care faceți o buclă în timp ce o resursă este invalizabilă sau ceva similar care aruncă o excepție, ați putea folosi ceva de genul

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

user12379095

Pentru mine, o buclă while tipică va fi ceva de genul acesta:

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

Aș putea include un bucla for..loop în cadrul buclei while, dacă situația justifică acest lucru, pentru a parcurge în buclă un alt set de condiții.

fr_andres

Funcția încorporată iter face exact acest lucru:

for x in iter(YOUR_FN, TERM_VAL):
    ...

De exemplu (testat în Py2 și 3):

class Easy:
  X = 0
  @classmethod
  def com(cls):
    cls.X += 1
    return cls.X

for x in iter(Easy.com, 10):
  print(">>>", x)

Dacă doriți să dați o condiție de terminare în loc de o valoare, puteți întotdeauna să stabiliți o egalitate și să cereți ca această egalitate să fie True.

martineau

Te-ai întrebat:

Ce pot face pentru a prinde excepția „stop iterație” și a întrerupe corect o buclă while?

Ați putea face acest lucru așa cum se arată mai jos și care utilizează, de asemenea, funcția expresiile de atribuire (cunoscut și ca „operatorul morsă”) care a fost introdus în Python 3.8:

list_of_ints = [1, 2, 3]
iterator = iter(list_of_ints)

try:
    while (element := next(iterator)):
        print(element)
except StopIteration:
    print("done")

O altă posibilitate (care ar funcționa de la Python 2.6 până la 3.x) ar fi să oferiți o opțiune default argument pentru funcția încorporată next() pentru a evita utilizarea funcției StopIteration excepția:

SENTINEL = object()  # Unique object.
list_of_ints = [1, 2, 3]
iterator = iter(list_of_ints)

while True:
    element = next(iterator, SENTINEL)
    if element is SENTINEL:
        break
    print(element)

print("done")