Restart generator when it is exhausted.

J. Cliff Dyer jcd at sdf.lonestar.org
Tue Apr 28 11:38:10 EDT 2009


On Tue, 2009-04-28 at 10:41 +0000, Duncan Booth wrote: 
> Lacrima <Lacrima.Maxim at gmail.com> wrote:
> 
> > If it is not possible what are common techniques to use iterator or
> > generator objects that allow restarting when it is needed?
> 
> The usual thing if you want to use the generator's output more than once  
> would be to convert the generator to a list, then you can iterate over it 
> as often as you want.
> 
> >>> a = ['a', 'b', 'c']
> >>> g = (i for i in a)
> >>> restartable = list(g)
> 
> If you want the output of the generator to potentially change each time you 
> iterate then you need to create a new generator.
> 

More verbosely, but without putting your generator in , you can use the
iterator protocol to create a reusable iterable:

An iterable is a class with an __iter__ method that returns an iterator.

So for example:

class Iterator(object):
    def __init__(self, filename):
        self.f = open(filename)
    
    def __iter__(self):
        return self

    def next(self):
        line = self.f.readline()
        if not line:
            raise StopIteration
        return line.strip()[:8]

is an iterator (which is also an iterable), which will grab each line of
a file, returning the first eight non-whitespace characters until the
file is used up.  Then the iterator is exhausted, and will continue to
raise StopIteration each time it is called.

class Iterable(object):
    def __init__(self, filename):
        self.filename = filename

    def __iter__(self):
        return Iterator(self.filename)

This is a reusable iterable which returns a new instance of the Iterator
class above each time it is exhausted.

So given a file hello.txt:

  Hello world
    Hola mundo
  Guten tag, weld.

The classes can be used as followed:
>>> iterator = Iterator('hello.txt')
>>> for i in xrange(3):
>>>     print "*** %d ***" % i
>>>     for j in iterator:
>>>         print j
*** 0 ***
Hello wo
Hola mun
Guten ta
*** 1 ***
*** 2 ***
>>> iterable = Iterable('hello.txt')
>>> for i in xrange(3):
>>>     print "*** %d ***" % i
>>>     for j in iterable:
>>>         print j
*** 0 ***
Hello wo
Hola mun
Guten ta
*** 1 ***
Hello wo
Hola mun
Guten ta
*** 2 ***
Hello wo
Hola mun
Guten ta

When Iterator hits a StopIteration, it passes out of the inner loop, and
when it comes back in, the inner loop calls iterator.__iter__(), and
gets the same exhausted iterator (which immediately breaks the inner
loop by raising StopIteration).  In Iterable, when the loop calls
iterable.__iter__(), it gets a fresh iterator, so it can loop over the
file again.

The important thing is that when you call x.__iter__() (which you do
when entering a loop), you get a fresh iterator that won't just call
StopIteration right away.

Cheers,
Cliff





More information about the Python-list mailing list