This class allows you to use generators as more list-like streams. The chief advantage is that it is impossible to iterate through a generator more than once, while a stream can be re-used like a list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
class _stream_iter: def __init__(self, s): self.stream = s self.i = 0 def next(self): try: val = self.stream[self.i] except IndexError: raise StopIteration self.i += 1 return val class stream(list): def __init__(self, iterator): list.__init__(self) self.it = iterator self.length = 0 self.done = 0 def __iter__(self): return _stream_iter(self) def __getitem__(self, i): if i >= self.length or self.length == 0: for j in range(i + 1 - self.length): self.append(self.it.next()) self.length = i+1 elif i < 0: for i in self.it: self.append(i) self.length = self.__len__() return list.__getitem__(self, i) def __getslice__(self, i, j): junk, junk = self[i], self[j] return list.__getslice__(self, i, j) def __repr__(self): return '<stream instance at 0x%x, %r materialized>' \ % (id(self), list.__repr__(self))
The chief application of a stream is cases where you have a generator you want to iterate through more than once. With just a generator, that is difficult:
>>> def upto(n): for i in range(n): yield i >>> to5 = upto(5) >>> for i in to5: if i == 3: break print i,
0 1 2
>>> for i in to5: print i,
The generator is stateful, so you can only iterate through it once. If you break off in the middle of a loop, the next call to its next() method will pick up from where it left off. With a stream, this is not the case:
>>> to5stream = genlib.stream(upto(5)) >>> for i in to5stream: if i == 3: break print i,
0 1 2
>>> for i in to5stream: print i,
0 1 2 3 4
The benefits of a generator are mostly preserved: the whole list result isn't materialized at once, but what is materialized is still stored.