Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
resetting a generator
#1
i would like to make a generator (or modify an existing one) to have a means to reset it to start all over at the beginning (whether it has finished or not). how would i go about doing that without making a new one?
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#2
I would define instead a class that obeys the iterator protocol, that is to say it has __iter__() and __next__() methods and the latter raises StopIteration when the iterator is exhausted. For example
class WalkList:
    def __init__(self, seq):
        if not isinstance(seq, list):
            raise TypeError('list argument expected, got', type(seq))
        self.seq = seq
        self.reset()
        
    def reset(self):
        self.ite = iter(self.seq)
        
    def __next__(self):
        return next(self.ite)
    
    def __iter__(self):
        return self
    
walk = WalkList(list(range(7)))

r = True
for item in walk:
    if r and item == 3:
        walk.reset()
        r = False
        continue
    print(item)
Output:
λ python3 paillasse/genstate.py 0 1 2 0 1 2 3 4 5 6
Reply
#3
i don't see "StopIteration" anywhere in that code. isn't __next__() suppose to raise StopIteration when the generator is exhausted? or do you expect next() to do that for you? would that mean self.ite is being updated as this iterates? if not, where is next() looking to know when it reaches the end?
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#4
The call to next() at line 12 will raise StopIteration at the end of the iteration.
Reply
#5
what i am wanting is for there to be some extra callable that causes the generator to reset during its iteration, much like your output example shows. my concern is how to communicate the "event" of calling that reset function to the generator so i can modify its state. if thinking so extra class it checks at each iteration for a flag that tells it to reset and another method to raise the flag. but how to do that with multiple instances of the generator, chains and so forth.
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#6
Skaperen Wrote:what i am wanting is for there to be some extra callable that causes the generator to reset during its iteration

If you really want a generator, you can pass an object as a parameter in the generator and invoke a method on that object. In the following example, I pass an object 'flag' to the generator 'mygen'. The generator calls flag.set(False) which returns True if the flag was previously set to True by another part of the code. Thus one can interfere with the inner mecanism of the generator
class Flag:
    """An object with a boolean member 'value'"""

    def __init__(self):
        self.value = False

    def set(self, value):
        """Sets our value to a new value and return the old value"""
        result = self.value
        self.value = value
        return result

def mygen(flag):
    while True:
        for i in range(10):
            if flag.set(False):  # if the flag was set to True, we reset the generator
                break
            yield i
        else:
            break

# test code

spam = True
flag = Flag()
for i in mygen(flag):
    if spam and i == 3:
        flag.set(True)  # this is how we tell the generator to reset itself
        spam = False
    print(i)
Another solution would be to use a generator as a coroutine by using the syntax y = yield x, but I think it's much harder to manage.

Output:
0 1 2 3 0 1 2 3 4 5 6 7 8 9
Reply
#7
how does y = yield x work? i've never seen anything like that.
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#8
To learn about it, you could start with this page by David Beazley.

Note that the word 'coroutine' has a different meaning in modern python where it refers to asynchronous coroutines. I don't know them well because I don't use them.

Coming back to the previous example, I managed to wrap things in a simple decorator @resetable. To define a resetable generator, you only need to add a first argument 'self' and you can call self.reset() inside the generator.
from functools import wraps

class Resetable:
    def __init__(self):
        self.flag = False
        self.iterator = None
        
    def reset(self, value=True):
        result = self.flag
        self.flag = bool(value)
        return result
    
    def __iter__(self):
        return self.iterator

def resetable(gen):
    @wraps(gen)
    def wrapper(*args, **kwargs):
        r = Resetable()
        r.iterator = gen(r, *args, **kwargs)
        return r
    return wrapper

@resetable
def mygen(self, n):
    while True:
        for i in range(n):
            if self.reset(False):
                break
            yield i
        else:
            break

if __name__ == '__main__':
    spam = True
    gen = mygen(7)
    for i in gen:
        if spam and i == 3:
            gen.reset()
            spam = False
        print(i)
I haven't tried this but in recent versions of python, you should be able to write for i in (gen := mygen(7)): ... instead of defining gen on the previous line.
Reply
#9
what my first thought was: the main loop of my generator consisting of a block where the yield statement would be found, would have other code that checks the reset flag and if that flag is up, do a reset block of code that lowers the flag and updates variables to reflect the reset state so the yield statement below it will yield the first item, again. the hard part is if the generator has already ended, which i presume has raised StopIteration. what i am curious about is: if you have an iterator driven loop where a generator is the iterator, does StopIteration get raised before or after the last pass of the body of the loop. i can imagine either scenario, so that does not confine the knowledge for me. it is in that body where the reset could be done, not after it. so if the scenario is to raise StopIteration after the last pass (which would not run that block, again), i think this could be done, because the iteration mechanism does not know it is the last pass during the last pass, so a reset during the "last" pass could make the generator still yield values for even more passes. the big worry is that it might be running the last pass after StopIteration has been raised, which probably cannot be undone.

(Nov-13-2019, 06:13 AM)Gribouillis Wrote: To learn about it, you could start with this page by David Beazley.

i dislike the way his source code operates on his web site. it forces the source to be downloaded and does not allow me to choose to just view it (as my site does).

[i used "dislike" instead of the 4-letter "h"-word since this is a family-oriented forum.]
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020