21

Does Python have anything in the fashion of a "redo" statement that exists in some languages?

(The "redo" statement is a statement that (just like "break" or "continue") affects looping behaviour - it jumps at the beginning of innermost loop and starts executing it again.)

Matjaž Leonardis
  • 311
  • 1
  • 2
  • 4
  • There are plenty of ways you can do this. For one, you could use a `while` loop and reset whatever your counter / condition is upon some evaluation. – miradulo Apr 12 '16 at 12:38
  • 4
    Never heard of such a thing. Sounds a lot like goto – Christopher Schneider Apr 12 '16 at 12:38
  • 1
    @ChristopherSchneider: Perl uses it (for what that's worth). Think a `continue` that doesn't perform the loop advancement step. Since it's tied to the loop itself, it's not morally distinct from `continue` and `break` really; if you accept them as something other than just `goto`, then `redo` is no worse (or better). – ShadowRanger Apr 12 '16 at 12:55

7 Answers7

10

No, Python doesn't have direct support for redo. One option would something faintly terrible involving nested loops like:

for x in mylist:
    while True:
        ...
        if shouldredo:
            continue  # continue becomes equivalent to redo
        ...
        if shouldcontinue:
            break     # break now equivalent to continue on outer "real" loop
        ...
        break  # Terminate inner loop any time we don't redo

but this mean that breaking the outer loop is impossible within the "redo-able" block without resorting to exceptions, flag variables, or packaging the whole thing up as a function.

Alternatively, you use a straight while loop that replicates what for loops do for you, explicitly creating and advancing the iterator. It has its own issues (continue is effectively redo by default, you have to explicitly advance the iterator for a "real" continue), but they're not terrible (as long as you comment uses of continue to make it clear you intend redo vs. continue, to avoid confusing maintainers). To allow redo and the other loop operations, you'd do something like:

# Create guaranteed unique sentinel (can't use None since iterator might produce None)
sentinel = object()
iterobj = iter(mylist)  # Explicitly get iterator from iterable (for does this implicitly)
x = next(iterobj, sentinel)  # Get next object or sentinel
while x is not sentinel:     # Keep going until we exhaust iterator
    ...
    if shouldredo:
        continue
    ...
    if shouldcontinue:
        x = next(iterobj, sentinel)  # Explicitly advance loop for continue case
        continue
    ...
    if shouldbreak:
        break
    ...
    # Advance loop
    x = next(iterobj, sentinel)

The above could also be done with a try/except StopIteration: instead of two-arg next with a sentinel, but wrapping the whole loop with it risks other sources of StopIteration being caught, and doing it at a limited scope properly for both inner and outer next calls would be extremely ugly (much worse than the sentinel based approach).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • This should be the accepted answer. A redo would not be very pythonic, and emulating your for-loop with and flag to redo if necessary will be easy to follow for other people. Thank you – ddelange May 18 '19 at 01:54
8

No, it doesn't. I would suggest using a while loop and resetting your check variable to the initial value.

count = 0
reset = 0
while count < 9:
   print 'The count is:', count
   if not someResetCondition:
       count = count + 1
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
cmaynard
  • 2,852
  • 2
  • 24
  • 34
  • Using `reset` doesn't replicate what `redo` does in other languages. `redo` is `continue` without the loop increment/advance step, but it doesn't restart the loop from the beginning; you'd just make the `count` increment optional, not have a `reset` variable. – ShadowRanger Apr 12 '16 at 13:12
  • Ah, I misread your initial statement "it jumps at the beginning" to mean the initial point, not just the top of the loop. I'll modify my answer. – cmaynard Apr 12 '16 at 13:20
  • I'm not the OP, it was their initial statement, not mine. I'll admit I could be misreading, but the only language I know of off-hand with `redo` is Perl, and it behaves this way. Note: The edited code is fine if you're replacing `for count in range(10):`, but it's not particularly generalizable to arbitrary iterables; my second code example in [my answer](https://stackoverflow.com/a/36574072/364696) is the fully generalized version. – ShadowRanger Apr 12 '16 at 13:23
1

This is my solution using iterators:

class redo_iter(object):
    def __init__(self, iterable):
        self.__iterator = iter(iterable)
        self.__started = False
        self.__redo = False
        self.__last = None
        self.__redone = 0
    def __iter__(self):
        return self
    def redo(self):
        self.__redo = True
    @property
    def redone(self):
        return self.__redone
    def __next__(self):
        if not (self.__started and self.__redo):
            self.__started = True
            self.__redone = 0
            self.__last = next(self.__iterator)
        else:
            self.__redone += 1
        self.__redo = False
        return self.__last


# Display numbers 0-9.
# Display 0,3,6,9 doubled.
# After a series of equal numbers print --
iterator = redo_iter(range(10))
for i in iterator:
    print(i)
    if not iterator.redone and i % 3 == 0:
        iterator.redo()
        continue
    print('---')
  • Needs explicit continue
  • redone is an extra feature
  • For Python2 use def next(self) instead of def __next__(self)
  • requires iterator to be defined before the loop
Piotr Ćwiek
  • 1,580
  • 13
  • 19
1

I just meet the same question when I study perl,and I find this page.

follow the book of perl:

my @words = qw(fred barney pebbles dino wilma betty);
my $error = 0;

my @words = qw(fred barney pebbles dino wilma betty);
my $error = 0;

foreach (@words){
    print "Type the word '$_':";
    chomp(my $try = <STDIN>);
    if ($try ne $_){
        print "Sorry - That's not right.\n\n";
        $error++;
        redo;
    }
}

and how to achieve it on Python ?? follow the code:

tape_list=['a','b','c','d','e']

def check_tape(origin_tape):
    errors=0
    while True:
        tape=raw_input("input %s:"%origin_tape)
        if tape == origin_tape:
            return errors
        else:
            print "your tape %s,you should tape %s"%(tape,origin_tape)
            errors += 1
            pass

all_error=0
for char in tape_list:
    all_error += check_tape(char)
print "you input wrong time is:%s"%all_error

Python has not the "redo" syntax,but we can make a 'while' loop in some function until get what we want when we iter the list.

1

Not very sophiscated but easy to read, using a while and an increment at the end of the loop. So any continue in between will have the effect of a redo. Sample to redo every multiple of 3:

redo = True # To ends redo condition in this sample only
i = 0
while i<10:
   print(i, end='')
   if redo and i % 3 == 0:
      redo = False # To not loop indifinively in this sample
      continue # Redo
   redo = True
   i += 1

Result: 00123345667899

Sec
  • 7,059
  • 6
  • 31
  • 58
Le Droid
  • 4,534
  • 3
  • 37
  • 32
0

There is no redo in python. A very understandable solution is as follow:

for x in mylist:
    redo = True
    while redo:
        redo = False

        If should_redo:
            redo = True

It's clear enough to do not add comments

Continue will work as if it was in the for loop

But break is not useable, this solution make break useable but the code is less clear.

yoh
  • 154
  • 1
  • 10
0

Here is a solution for python 3.8+ since now we have the := operator:

for key in mandatory_attributes:  # example with a dictionary
    while not (value := input(f"{key} (mandatory): ")):
        print("You must enter a value")

    mandatory_attributes[key] = value
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Molesox
  • 21
  • 1
  • 3