Emulować pętlę „do-while” w Pythonie?


797

Muszę emulować pętlę „do-while” w programie Python. Niestety następujący prosty kod nie działa:

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"

Zamiast „1,2,3, gotowe” wypisuje następujące dane wyjściowe:

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

Co mogę zrobić, aby złapać wyjątek „zatrzymaj iterację” i przerwać pętlę while?

Przykład, dlaczego taka rzecz może być potrzebna, pokazano poniżej jako pseudokod.

Maszyna stanowa:

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 ... To nie jest właściwe „robić”; to po prostu „zrobić na zawsze”. Co jest złego w „while True” i „break”?
S.Lott,

70
S. Lott: Jestem prawie pewien, że jego pytanie dotyczyło sposobu wykonania w Pythonie. Nie spodziewałbym się więc, że jego kod będzie całkowicie poprawny. Poza tym jest bardzo blisko do zrobienia ... sprawdza stan na końcu pętli „na zawsze”, aby sprawdzić, czy powinien się wyrwać. To nie jest „na zawsze”.
Tom

4
więc ... Twój początkowy przykładowy kod faktycznie działa dla mnie bez problemu i nie otrzymuję tego śledzenia. to jest odpowiedni idiom dla pętli do while, w której warunkiem przerwania jest wyczerpanie iteratora. zazwyczaj ustawiasz s=i.next()raczej niż Brak i ewentualnie wykonujesz początkową pracę zamiast po prostu uczynić pierwsze przejście przez pętlę bezużytecznym.
underrun

3
@underrun Niestety, post nie jest oznaczony, która wersja Pythona była używana - oryginalny fragment działa również dla mnie przy użyciu wersji 2.7, prawdopodobnie z powodu aktualizacji samego języka Python.
Hannele,

Odpowiedzi:


984

Nie jestem pewien, co próbujesz zrobić. Możesz zaimplementować następującą pętlę:

while True:
  stuff()
  if fail_condition:
    break

Lub:

stuff()
while not fail_condition:
  stuff()

Co robisz, próbując użyć pętli do while, aby wydrukować rzeczy z listy? Dlaczego nie po prostu użyć:

for i in l:
  print i
print "done"

Aktualizacja:

Czy masz listę linii? I chcesz to powtarzać? Co powiesz na:

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

Czy to wydaje się być czymś zbliżonym do tego, czego byś chciał? W twoim przykładzie kodu byłoby to:

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
muszę utworzyć maszynę stanu. W maszynie stanów normalnym przypadkiem jest ponowna ocena instrukcji CURRENT, więc muszę „kontynuować” bez iteracji następnego elementu. Nie wiem, jak zrobić coś takiego w 'for s in l:' iteracja :(. W pętli do-while „kontynuuj” ponownie oceni bieżący element, iteracja na końcu
grigoryvp

Czy masz na myśli śledzenie swojego miejsca na liście? W ten sposób, kiedy powrócisz w tym samym stanie, możesz zacząć od miejsca, w którym skończyłeś? Daj trochę więcej kontekstu. Wygląda na to, że lepiej byłoby użyć indeksu na liście.
Tom

Dzięki, skomentowałem twój pseudokod ... twój przykład wydaje się trochę zły, ponieważ wydajesz się obsługiwać „//” w ten sam sposób, bez względu na to, w jakim jesteś stanie. Czy to jest prawdziwy kod, w którym przetwarzasz komentarze? Co jeśli masz ciągi znaków z ukośnikami? tj .: wydrukuj „bla // <- czy to cię wkurza?”
Tom

4
Szkoda, że ​​Python nie ma pętli „do-while”. Python jest SUCHY, co?
Kr0e

43
Zobacz także PEP 315 dla oficjalnej postawy / uzasadnienia: „Użytkownikom tego języka zaleca się stosowanie formy while-True z wewnętrzną przerwą if, gdy byłaby odpowiednia pętla do-while”.
dtk

310

Oto bardzo prosty sposób emulacji pętli „do-while”:

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

Kluczowe cechy pętli „do-while” polegają na tym, że ciało pętli zawsze wykonuje się co najmniej raz, a warunek jest oceniany na dole ciała pętli. Przedstawiona tutaj struktura kontrolna realizuje oba z nich bez potrzeby wyjątków lub instrukcji break. Wprowadza jedną dodatkową zmienną logiczną.


11
Nie zawsze dodaje dodatkową zmienną boolowską. Często istnieje już coś (y), których stan można przetestować.
martineau,

14
Powodem, dla którego najbardziej podoba mi się to rozwiązanie, jest to, że nie dodaje on kolejnego warunku, nadal jest to tylko jeden cykl, a jeśli wybierzesz dobre imię dla zmiennej pomocnika, cała struktura będzie dość wyraźna.
Roberto

4
UWAGA: Chociaż dotyczy to pierwotnego pytania, to podejście jest mniej elastyczne niż stosowanie break. W szczególności, jeśli potrzebna jest logika PO test_loop_condition(), która nie powinna zostać wykonana po zakończeniu, należy ją zapakować if condition:. BTW, conditionjest niejasne. Bardziej opisowy: morelub notDone.
ToolmakerSteve

7
@ToolmakerSteve Nie zgadzam się. Rzadko używam breakw pętlach i kiedy napotykam go w kodzie, który utrzymuję, stwierdzam, że pętla najczęściej mogłaby zostać napisana bez niego. Zaprezentowane rozwiązanie jest, według IMO, najczystszym sposobem reprezentowania konstrukcji do while w Pythonie.
nonsensickle

1
Idealnie, warunek zostanie nazwany czymś opisowym, na przykład has_no_errorslub end_reached(w którym to przypadku uruchomi się pętlawhile not end_reached
JosiahYoder-deactive, z wyjątkiem ..

74

Mój kod poniżej może być użyteczną implementacją, podkreślając główną różnicę między nimi vs tak jak rozumiem.

Tak więc w tym przypadku zawsze przechodzisz przez pętlę przynajmniej raz.

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

2
Prawidłowa odpowiedź, kłócę się. Dodatkowo pozwala uniknąć złamania , dla bezpiecznego użycia w blokach try / oprócz.
Zv_oDD,

czy jit / optymalizator unika ponownego testowania first_pass po pierwszym przejściu? w przeciwnym razie byłby to irytujący, choć może niewielki, problem z wydajnością
markhahn

2
@markhahn to jest naprawdę niewielkie, ale jeśli zależy od takich szczegółów, można intervert do 2 wartości logicznych w pętli: while condition or first_pass:. Następnie conditionjest zawsze oceniany jako pierwszy i ogólniefirst_pass jest oceniany tylko dwukrotnie (pierwsza i ostatnia iteracja). Nie zapomnij zainicjować conditionprzed pętlą tego, co chcesz.
pLOPeGG

HM, ciekawe Właściwie wybrałem odwrotnie, aby nie musiał inicjować warunku, a zatem wymagał minimalnych zmian w kodzie. To powiedziawszy, rozumiem twój punkt
evan54

33

Wyjątek spowoduje przerwanie pętli, więc równie dobrze możesz obsługiwać ją poza pętlą.

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

Myślę, że problem z twoim kodem polega na tym, że zachowanie breakwewnątrz exceptnie jest zdefiniowane. Zasadniczo breakidzie tylko o jeden poziom wyżej, więc np. breakWnętrze tryidzie bezpośrednio do finally(jeśli istnieje) poza try, ale nie poza pętlę.

Powiązane PEP: http://www.python.org/dev/peps/pep-3136
Podobne pytanie: Wyjście z zagnieżdżonych pętli


8
Dobrą praktyką jest jednak umieszczanie w instrukcji try tylko tego, co ma rzucić wyjątek, aby nie złapać niepożądanych wyjątków.
Paggas,

7
@PiPeep: RTFM, wyszukaj EAFP.
vartec,

2
@PiPeep: nie ma problemu, pamiętaj tylko, że to, co jest prawdziwe w niektórych językach, może nie być prawdą w przypadku innych. Python jest zoptymalizowany pod kątem intensywnego korzystania z wyjątków.
vartec,

5
break i Kontynuuj są doskonale zdefiniowane w dowolnej klauzuli instrukcji try / wyjątek / w końcu. Po prostu je ignorują i albo wybijają się, albo przechodzą do następnej iteracji zawierającej while lub pętli, stosownie do przypadku. Jako elementy konstrukcji zapętlających są one odpowiednie tylko dla instrukcji while i for, i wywołują błąd składniowy, jeśli napotkają instrukcję klasy lub def, zanim dotrą do najbardziej wewnętrznej pętli. Ignorują instrukcje if, with i try.
ncoghlan

1
.. co jest ważnym przypadkiem
javadba

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

->

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

Możesz wykonać funkcję:

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

Ale 1) To brzydkie. 2) Warunek powinien być funkcją z jednym parametrem, który powinien być wypełniony przez rzeczy (to jedyny powód, dla którego nie należy używać klasycznej pętli while).


5
Pisanie while True: stuff(); if not condition(): breakto bardzo dobry pomysł. Dziękuję Ci!
Noctis Skytower

2
@ZeD, dlaczego 1) jest brzydki? Jest całkiem ok, IMHO
Sergey Lossev

@SergeyLossev Trudno będzie zrozumieć logikę programu, ponieważ na początku wygląda na nieskończoną pętlę, jeśli pomiędzy nimi jest dużo kodu „rzeczy”.
exic

16

Oto bardziej szalone rozwiązanie o innym wzorze - wykorzystujące coroutines. Kod jest nadal bardzo podobny, ale z jedną ważną różnicą; w ogóle nie ma warunków wyjścia! Coroutine (tak naprawdę łańcuch coroutines) po prostu przestaje, kiedy przestajesz karmić go danymi.

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)

Powyższy kod zbiera wszystkie żetony jak w krotki tokensi zakładam nie ma różnicy pomiędzy .append()i .add()w oryginalnym kodzie.


4
Jak napisałbyś to dzisiaj w Pythonie 3.x?
Noctis Skytower

13

Sposób, w jaki to zrobiłem, jest następujący ...

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

Wydaje mi się, że to proste rozwiązanie, jestem zaskoczony, że jeszcze go tu nie widziałem. Można to oczywiście również odwrócić

while not condition:

itp.


Mówisz: „Jestem zaskoczony, że już go tu nie widziałem” - ale nie widzę żadnej różnicy w stosunku do, powiedzmy, roztworu w proszku z 2010 roku. Jest dokładnie taki sam. ("condition = True while condition: # body loop here condition = test_loop_condition () # end of loop")
cslotty 12.1119

10

dla pętli do - while zawierającej instrukcje 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()

alternatywnie, gdy nie ma potrzeby stosowania klauzuli „w końcu”

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. To wydaje się znacznie brzydsze niż użycie przerwy.
mattdm

5
To sprytne, ale wymaga stufffunkcji lub powtarzania treści kodu.
Noctis Skytower

12
Wszystko, co jest potrzebne, to while condition:dlatego, że is Trueimplikowane.
martineau,

2
to się nie powiedzie, jeśli conditionzależy od jakiejś wewnętrznej zmiennej stuff(), ponieważ ta zmienna nie jest w tym momencie zdefiniowana.
jo

5
Nie ta sama logika, ponieważ przy ostatniej iteracji, gdy warunek! = Prawda: Wywołuje kod po raz ostatni. Where as a Do While najpierw wywołuje kod, a następnie sprawdza stan przed ponownym uruchomieniem. Do While: wykonaj blok raz; następnie sprawdź i uruchom ponownie , ta odpowiedź: sprawdź i uruchom ponownie; następnie wykonaj blok kodu raz . Duża różnica!
Zv_oDD,

7

Szybki hack:

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

Użyj tak:

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

3

Dlaczego po prostu nie zrobisz

for s in l :
    print s
print "done"

?


1
muszę utworzyć maszynę stanu. W maszynie stanów normalnym przypadkiem jest ponowna ocena instrukcji CURRENT, więc muszę „kontynuować” bez iteracji następnego elementu. Nie wiem, jak zrobić coś takiego w 's' w l: 'iteracja :(. W pętli do-while „kontynuuj” ponownie oceni bieżący element, iteracja na końcu.
grigoryvp

następnie, czy możesz zdefiniować pseudo-kod dla swojej maszyny stanów, abyśmy mogli podpowiedzieć Ci najlepsze rozwiązanie w Pythonie? Nie wiem wiele o automatach stanowych (i prawdopodobnie nie jestem jedyny), więc jeśli powiesz nam trochę o swoim algorytmie, łatwiej będzie nam ci pomóc.
Martin

Pętla For nie działa dla takich rzeczy jak: a = fun (), podczas gdy a == 'zxc': sleep (10) a = fun ()
harry

To całkowicie pomija sens sprawdzania warunku logicznego
javadba

1

Sprawdź, czy to pomoże:

Ustaw flagę w module obsługi wyjątków i sprawdź ją przed rozpoczęciem pracy na s.

flagBreak = false;
while True :

    if flagBreak : break

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

print "done"

3
Można to uprościć, używając while not flagBreak:i usuwając if (flagBreak) : break.
martineau,

1
flagUnikam zmiennych o nazwie - Nie jestem w stanie wywnioskować, co oznacza wartość True lub False. Zamiast tego użyj donelub endOfIteration. Kod zmienia się w while not done: ....
IceArdor,

1

Jeśli masz scenariusz, w którym zapętlasz się, gdy zasób jest niedostępny lub coś podobnego, co rzuca wyjątek, możesz użyć czegoś takiego

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

Dla mnie typowa pętla while będzie taka:

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

Mógłbym również włączyć pętlę for .. w pętli while, jeśli sytuacja tego wymaga, do zapętlenia innego zestawu warunków.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.