import itertools class SavedIterable (object): """Wrap an iterable and cache it. The SavedIterable can be accessed streamingly, while still being incrementally cached. Later attempts to iterate it will access the whole of the sequence. When it has been cached to its full extent once, it reduces to a thin wrapper of a sequence iterator. The SavedIterable will pickle into a list. >>> s = SavedIterable(xrange(5)) >>> iter(s).next() 0 >>> list(s) [0, 1, 2, 3, 4] >>> iter(s) # doctest: +ELLIPSIS <listiterator object at 0x...> >>> import pickle >>> pickle.loads(pickle.dumps(s)) [0, 1, 2, 3, 4] >>> u = SavedIterable(xrange(5)) >>> one, two = iter(u), iter(u) >>> one.next(), two.next() (0, 0) >>> list(two) [1, 2, 3, 4] >>> list(one) [1, 2, 3, 4] >>> SavedIterable(range(3)) [0, 1, 2] """ def __new__(cls, iterable): if isinstance(iterable, list): return iterable return object.__new__(cls) def __init__(self, iterable): self.iterator = iter(iterable) self.data = [] def __iter__(self): if self.iterator is None: return iter(self.data) return self._incremental_caching_iter() def _incremental_caching_iter(self): indices = itertools.count() while True: idx = indices.next() try: yield self.data[idx] except IndexError: pass else: continue if self.iterator is None: return try: x = self.iterator.next() self.data.append(x) yield x except StopIteration: self.iterator = None def __reduce__(self): # pickle into a list with __reduce__ # (callable, args, state, listitems) return (list, (), None, iter(self)) if __name__ == '__main__': import doctest doctest.testmod()