1077

I need to emulate a do-while loop in a Python program. Unfortunately, the following straightforward code does not work:

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"

Instead of "1,2,3,done", it prints the following 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
']

What can I do in order to catch the 'stop iteration' exception and break a while loop properly?

An example of why such a thing may be needed is shown below as pseudocode.

State machine:

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
martineau
  • 119,623
  • 25
  • 170
  • 301
grigoryvp
  • 40,413
  • 64
  • 174
  • 277
  • 7
    Um... That's not a proper "do-while"; that's simply a "do-forever". What's wrong with "while True" and "break"? – S.Lott Apr 13 '09 at 15:43
  • 102
    S. Lott: I'm pretty sure his question was about *how* to implement do while in python. So, I wouldn't expect his code to be completely correct. Also, he is very close to a do while... he is checking a condition at the end of the "forever" loop to see if he should break out. It's not "do-forever". – Tom Apr 13 '09 at 18:43
  • 6
    so ... your initial example code actually works for me with no problem and i don't get that traceback. that's a proper idiom for a do while loop where the break condition is iterator exhaustion. typically, you'd set `s=i.next()` rather than None and possibly do some initial work rather than just make your first pass through the loop useless though. – underrun Sep 21 '11 at 19:31
  • 5
    @underrun Unfortunately, the post is not tagged with which version of Python was being used - the original snippet works for me too using 2.7, presumably due to updates to the Python language itself. – Hannele Oct 02 '12 at 17:55

21 Answers21

1285

I am not sure what you are trying to do. You can implement a do-while loop like this:

while True:
  stuff()
  if fail_condition:
    break

Or:

stuff()
while not fail_condition:
  stuff()

What are you doing trying to use a do while loop to print the stuff in the list? Why not just use:

for i in l:
  print i
print "done"

Update:

So do you have a list of lines? And you want to keep iterating through it? How about:

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

Does that seem like something close to what you would want? With your code example, it would be:

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
jesugmz
  • 2,320
  • 2
  • 18
  • 33
Tom
  • 21,468
  • 6
  • 39
  • 44
  • 5
    i need to create a state machine. In state machine it's a normal case to re-evaluate CURRENT statement, so i need to 'continue' without iterating next item. I don't know how to do such thing in 'for s in l:' iteration :(. In do-while loop, 'continue' will re-evaluate current item, iteration at end – grigoryvp Apr 13 '09 at 06:41
  • Do you mean you need to keep track of your place in the list? That way when you return the same state, you can pick up where you left off? Give a bit more context. It seems like you might be better off using an index into the list. – Tom Apr 13 '09 at 06:48
  • Thanks, I commented on your pseudocode... your example seems sort of bad since you seem to handle "//" the same way no matter what state you are in. Also, is this real code where you are processing comments? What if you have strings with slashes? ie: print "blah // <-- does that mess you up?" – Tom Apr 13 '09 at 07:44
  • That was a sample pseudocode, just to illustrate a concept why it is needed to process same line more than once. But your sample with for ... + While True seems VERY good to me and solves the exact task i have: 'break' goes to next item and 'continue' re-evaluates same item. – grigoryvp Apr 13 '09 at 08:32
  • I like the `while True: stuff(); if fail_condition: break` idea. Very nice! – Noctis Skytower Sep 11 '12 at 20:05
  • 10
    It's a shame that python does not have a do-while loop. Python is DRY, eh ? – Kr0e Aug 24 '13 at 08:53
  • 88
    Also see [PEP 315](https://www.python.org/dev/peps/pep-0315/) for the official stance/justification: "Users of the language are advised to use the while-True form with an inner if-break when a do-while loop would have been appropriate." – dtk Aug 15 '16 at 12:47
  • 1
    While [PEP 315](https://www.python.org/dev/peps/pep-0315/) might seem related, that proposal is not for a do while loop as found in most other languages: looped statements both precede and follow the while statement. No wonder it was rejected. – egbit Apr 17 '22 at 11:34
390

Here's a very simple way to emulate a do-while loop:

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

The key features of a do-while loop are that the loop body always executes at least once, and that the condition is evaluated at the bottom of the loop body. The control structure show here accomplishes both of these with no need for exceptions or break statements. It does introduce one extra Boolean variable.

martineau
  • 119,623
  • 25
  • 170
  • 301
powderflask
  • 3,925
  • 1
  • 15
  • 2
  • 17
    It doesn't always add an extra boolean variable. Often there's something(s) that already exist whose state can be tested. – martineau Oct 02 '12 at 17:32
  • 22
    The reason I like this solution the most is that it doesn't add another condition, it still is just one cycle, and if you pick a good name for the helper variable the whole structure is quite clear. – Roberto Oct 08 '13 at 21:04
  • 9
    NOTE: While this does address the original question, this approach is less flexible than using `break`. Specifically, if there is logic needed AFTER `test_loop_condition()`, that should not be executed once we are done, it has to be wrapped in `if condition:`. BTW, `condition` is vague. More descriptive: `more` or `notDone`. – ToolmakerSteve Dec 15 '13 at 00:30
  • 10
    @ToolmakerSteve I disagree. I rarely use `break` in loops and when I encounter it in code that I maintain I find that the loop, most often, could have been written without it. The presented solution is, IMO, the *clearest* way to represent a do while construct in python. – nonsensickle Sep 24 '15 at 23:48
  • 1
    Ideally, condition will be named something descriptive, like `has_no_errors` or `end_reached` (in which case the loop would start `while not end_reached` – Josiah Yoder Sep 28 '15 at 20:27
  • this causes pylint C0103 error, due to condition being considered a constant? – Luke Griffith Nov 12 '16 at 15:31
  • But what if condition is lengthy? say a very long one liner – Ofer Rahat Oct 18 '17 at 08:26
  • @LukeGriffith Ideally you wouldn't use this outside of a function so condition is coming back as a constant. From another answer: 'avoid module level variables, by wrapping them into a function.' at https://stackoverflow.com/questions/41204932/python-pylint-c0103invalid-constant-name – Mitchell Walls Jul 12 '18 at 17:01
  • @MitchellWalls: Wrapping them in functions to avoid what's essentially a bug in PyLint is a terrible suggestion. – martineau Mar 13 '21 at 09:56
  • @martineau Explain to me how this is a PyLint bug? I don't see it. My mostly naive assessment to the original comment is to say yes use functions it is a good habit. I don't see how this "is a terrible suggestion". Please be more helpful and explain. – Mitchell Walls Mar 13 '21 at 15:24
  • @MitchellWalls: PEP 8 does not say that all module level variables should be capitalized, only those that are [constants](https://www.python.org/dev/peps/pep-0008/#constants). Actually it's more of a limitation of static code checking (rather than a bug) — it would be very difficult for it to determine the variable's intent. That means that wrapping them in functions would be doing nothing but introduce overhead to get around a limitation of the tool. See the comments under this [answer](https://stackoverflow.com/a/41217309/355230), especially the one about how it obfuscates code. – martineau Mar 13 '21 at 18:09
  • @nurettin Because the code presented will always execute the loop body at least once, *before* checking `test_loop_condition()`. – CrazyChucky Mar 21 '22 at 15:37
101

My code below might be a useful implementation, highlighting the main difference between vs as I understand it.

So in this one case, you always go through the loop at least once.

first_pass = True
while first_pass or condition:
    first_pass = False
    do_stuff()
evan54
  • 3,585
  • 5
  • 34
  • 61
  • 4
    Correct answer, I'de argue. Plus it avoids **break**, for safe use in try/except blocks. – Zv_oDD Feb 26 '16 at 19:41
  • does the jit/optimizer avoid re-testing first_pass after the first pass? otherwise, it would be an annoying, though perhaps minor, performance issue – markhahn Dec 12 '18 at 22:17
  • 7
    @markhahn this is really minor but if you care of such details, you can intervert the 2 booleans in the loop: `while condition or first_pass:`. Then `condition`is always evaluated first and overall `first_pass` is evaluated only twice (first and last iteration). Don't forget to initialize `condition` before the loop to whatever you want. – Thibault D. May 03 '19 at 12:36
  • 1
    HM, interesting I actually had picked the other way round purposely to not have to initialise condition and thus requiring minimal changes to the code. That said I see your point – evan54 May 04 '19 at 00:10
  • The problem here is that first_pass is set to `False` in every loop now. – Akhil Nambiar Dec 03 '20 at 09:50
  • 2
    @AkhilNambiar There's no problem with that? It's not the first pass... after the first pass. – Simply Beautiful Art Dec 17 '20 at 22:50
38
do {
  stuff()
} while (condition())

->

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

You can do a function:

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

But 1) It's ugly. 2) Condition should be a function with one parameter, supposed to be filled by stuff (it's the only reason not to use the classic while loop.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ZeD
  • 541
  • 3
  • 3
35

Exception will break the loop, so you might as well handle it outside the loop.

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

I guess that the problem with your code is that behaviour of break inside except is not defined. Generally break goes only one level up, so e.g. break inside try goes directly to finally (if it exists) an out of the try, but not out of the loop.

Related PEP: http://www.python.org/dev/peps/pep-3136
Related question: Breaking out of nested loops

Community
  • 1
  • 1
vartec
  • 131,205
  • 36
  • 218
  • 244
  • 10
    It's good practice though to only have inside the try statement what you expect to throw your exception, lest you catch unwanted exceptions. – Paggas Nov 02 '09 at 18:10
  • Writing code to expect exceptions is a poor programming practice, and in many JITed languages (I know, python is not JITed, but you don't want to get into the habit), it results in horrid performance. – bgw Nov 03 '10 at 03:04
  • 3
    @PiPeep: no problem, just keep in mind, that what's true for some languages, may not be true for other. Python is optimized for intensive use of exceptions. – vartec Nov 10 '10 at 16:30
  • 7
    break and continue are perfectly well-defined in any clause of a try/except/finally statement. They simply ignore them, and either break out of or move on to the next iteration of the containing while or for loop as appropriate. As components of the looping constructs, they're only relevant to while and for statements, and trigger a syntax error if they run into a class or def statement before reaching the innermost loop. They ignore if, with and try statements. – ncoghlan Feb 18 '11 at 06:41
  • all this machinery to emulate a simple `do while`. Why does python lack these things? – WestCoastProjects Nov 24 '18 at 14:17
  • This answer is best because it actually points out what is wrong with the OP's code – jamesblacklock Feb 06 '19 at 15:12
  • @javadba The reason for that is actually quite the opposite: Why do other languages feel the need to use `do while` loops? They are never useful, as the only difference between `do while` and `while` is whether the statements within the block are executed at least once. – 1313e Mar 21 '19 at 05:16
  • 3
    .. which is an important case – WestCoastProjects Mar 21 '19 at 05:37
  • 1
    @1313e One common use case is retrieving paginated results of a query: If this is the first page of results, or a previous page of results indicated that there are more results to fetch, fetch another page of results. – Damian Yerrick May 21 '19 at 17:29
  • @WestCoastProjects Because it doesn't have to be this complicated. The idiomatic way is `while True: do_stuff(); if condition: break`. – BlackJack Jun 28 '22 at 11:09
  • @BlackJack Which is a lousy construct vs other languages. `python` has been my primary language for 3 years (due to data science libraries only available in this language) and I have done heavy duty projects for the prior 8 years. i'm no noobie at all - but the language *is* weak . The main positive is `numpy` and its array indexing – WestCoastProjects Jun 28 '22 at 14:28
  • Lousy is a bit harsh and uncalled for. It is almost like a DO … WHILE or DO … UNTIL loop in other languages. The difference is really minor. – BlackJack Jun 28 '22 at 19:50
18

I believe that this do-while simulation on python has a syntax format closest to the do-while structure format present in C and Java.

do = True
while do:
    [...]
    do = <condition>
17

Here is a crazier solution of a different pattern -- using coroutines. The code is still very similar, but with one important difference; there are no exit conditions at all! The coroutine (chain of coroutines really) just stops when you stop feeding it with data.

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)

The code above collects all tokens as tuples in tokens and I assume there is no difference between .append() and .add() in the original code.

u0b34a0f6ae
  • 48,117
  • 14
  • 92
  • 101
17

The way I've done this is as follows...

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

This seems to me to be the simplistic solution, I'm surprised I haven't seen it here already. This can obviously also be inverted to

while not condition:

etc.

Gareth Lock
  • 215
  • 2
  • 4
  • 4
    You say "I'm surprised I haven't seen it here already" - but I don't see any difference from, let's say, powderflask's solution from 2010. It's exactly the same. ("condition = True while condition: # loop body here condition = test_loop_condition() # end of loop") – cslotty Nov 12 '19 at 09:53
10

Python 3.8 has the answer.

It's called assignment expressions. from the documentation:

# Loop over fixed length blocks
while (block := f.read(256)) != '':
    process(block)
Jonathan Shemer
  • 291
  • 4
  • 5
  • 6
    Nope. `do` _body_ `while` _condition_ first executes the _body_ and then evaluates the _condition_. your construct first checks the condition. it's a while ... do loop. – Susanne Oberhauser Mar 14 '22 at 15:30
  • I would prefer using `functools.partial()` and `iter()` for this: `for block in iter(partial, file.read, 256), ""): process(block)`. – BlackJack Jun 28 '22 at 11:14
  • A better solution would be: `do = True; while do: ; something(); if (do := condition()): complain();` – luke Nov 20 '22 at 06:45
  • This is the tipical example where you run the code in the "while" statement, therefore is similar to the do/while. On top with this, you might get the output. I would use this and put the required to run once in the function evaluated by while and the rest after. – BorjaEst Dec 02 '22 at 16:40
8

for a do - while loop containing try statements

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

alternatively, when there's no need for the 'finally' clause

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
martineau
  • 119,623
  • 25
  • 170
  • 301
Mark
  • 2,196
  • 1
  • 14
  • 8
8

While loop:

while condition:
  print("hello")
  

Do while loop:

while True:
  print("hello")
  if not condition:
    break

Also you can use any true boolean value as condition:

while 1:
  print("hello")
  if not condition:
    break

Another variant:

check = 1
while check:
    print("hello")
    check = condition
nehem
  • 12,775
  • 6
  • 58
  • 84
6

Quick hack:

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

Use like so:

>>> x = 10
>>> def f():
...     global x
...     x = x - 1
>>> def c():
        global x
        return x > 0
>>> dowhile(f, c)
>>> print x
0
Naftuli Kay
  • 87,710
  • 93
  • 269
  • 411
4
while condition is True: 
  stuff()
else:
  stuff()
MuSheng
  • 341
  • 2
  • 5
  • 8
    Ew. That seems significantly uglier than using a break. – mattdm Jan 26 '12 at 14:42
  • 5
    That is clever, but it requires `stuff` to be a function or for the code body to be repeated. – Noctis Skytower Sep 11 '12 at 20:08
  • 13
    All that's needed is `while condition:` because `is True` is implied. – martineau Oct 02 '12 at 18:15
  • 2
    this fails if `condition` depends on some inner variable of `stuff()`, because that variable is not defined at that moment. – yo' Feb 25 '14 at 20:23
  • 8
    Not the same logic, because on the last iteration when condition != True : It calls the code a final time. Where as a ***Do While***, calls the code once first, then checks condition before re-running. Do While : **execute block once; then check and re-run**, this answer: **check and re-run; then execute code block once**. Big difference! – Zv_oDD Feb 26 '16 at 19:34
4

Why don't you just do

for s in l :
    print s
print "done"

?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Martin
  • 5,954
  • 5
  • 30
  • 46
  • 1
    i need to create a state machine. In state machine it's a normal case to re-evaluate CURRENT statement, so i need to 'continue' without iterating next item. I don't know how to do such thing in 'for s in l:' iteration :(. In do-while loop, 'continue' will re-evaluate current item, iteration at end. – grigoryvp Apr 13 '09 at 06:26
  • then, can you define some pseudo-code for your state machine, so we can hint you towards the best pythonic solution ? I don't know much about state machines(and am probably not the only one), so if you tell us a bit about your algorithm, this will be easier for us to help you. – Martin Apr 13 '09 at 06:48
  • For loop does not work for things like: a = fun() while a == 'zxc': sleep(10) a = fun() – harry Sep 19 '13 at 07:26
  • This completely misses the point of checking a boolean condition – WestCoastProjects Nov 24 '18 at 14:16
1

You wondered:

What can I do in order to catch the 'stop iteration' exception and break a while loop properly?

You could do it as shown below and which also makes use of the assignment expressions feature (aka “the walrus operator”) that was introduced in 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")

Another possibility (that would work from Python 2.6 to 3.x) would be to provide a default argument to the built-in next() function to avoid the StopIteration exception:

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")
martineau
  • 119,623
  • 25
  • 170
  • 301
1

You can clean things up a bit with the walrus operator in Python 3.8:

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

_sentinel = object()
while True:
    if (i := next(iterator, _sentinel)) is _sentinel:
        break

    print(i)

This has no duplicated logic outside the while. It also handles values in the list that evaluate to False.

Trenton
  • 11,678
  • 10
  • 56
  • 60
0

If you're in a scenario where you are looping while a resource is unavaliable or something similar that throws an exception, you could use something like

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
  • 667
  • 2
  • 14
  • 27
0

For me a typical while loop will be something like this:

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

I could include a for..loop within the while loop as well, if situation so warrants, for looping through another set of condition.

0
while True:
    try:
        # stuff
        stuff_1()
        if some_cond:
            continue
        if other_cond:
            break
        stuff_2()
    finally:
        # condition
        if not condition:
            break
  • [x] condition checked only after running stuff
  • [x] stuff is not a function call
  • [x] condition is not a function call
  • [x] stuff can contain flow control
  • [ ] Avoid checking condition if stuff called break (can be done with another boolean)
dmcontador
  • 660
  • 1
  • 8
  • 18
0

See if this helps :

Set a flag inside the exception handler and check it before working on the s.

flagBreak = false;
while True :

    if flagBreak : break

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

print "done"
martineau
  • 119,623
  • 25
  • 170
  • 301
Nrj
  • 6,723
  • 7
  • 46
  • 58
  • 3
    Could be simplified by using `while not flagBreak:` and removing the `if (flagBreak) : break`. – martineau Oct 02 '12 at 18:23
  • 3
    I avoid variables named `flag`--I am unable to infer what a True value or False value mean. Instead, use `done` or `endOfIteration`. The code turns into `while not done: ...`. – IceArdor Mar 11 '14 at 20:03
-2

The built-in iter function does specifically that:

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

E.g. (tested in Py2 and 3):

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

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

If you want to give a condition to terminate instead of a value, you always can set an equality, and require that equality to be True.

fr_andres
  • 5,927
  • 2
  • 31
  • 44