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,
4
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.