[Python-ideas] Add lookahead iterator (peeker) to itertools

Terry Reedy tjreedy at udel.edu
Mon Feb 25 04:41:48 CET 2013


An iterator iter represents the remainder of some collection, concrete 
or not, finite or not. If the remainder is not empty, its .__next__ 
method selects one object, mutates iter to represent the reduced 
remainder (now possibly empty), and returns the one item.

At various times, people have asked for the ability to determine whether 
an iterator is exhausted, whether a next() call will return or raise. If 
it will return, people have also asked for the ability to peek at what 
the return would be, without disturbing what the return will be. For 
instance, on the 'iterable.__unpack__ method' Alex Stewart today wrote:
 > The problem is that there is no standard way to query an iterator
 > to find out if it has more data available without automatically
 > consuming that next data element in the rocess.

It turns out that there is a solution that gives the ability to both 
test emptiness (in the standard way) and peek ahead, without modifying 
the iterator protocol. It merely require a wrapper iterator, much like 
the ones in itertools. I have posted one before and give my current 
version below.

Does anyone else this should be added to itertools? It seems to not be 
completely obvious to everyone, is more complex that some of the 
existing itertools, and cannot be composed from them either. (Nor can it 
be written as a generator function.)

Any of the names can be changed. Perhaps the class should be 'peek' and 
the lookahead object something else. The sentinel should be read-only if 
possible. I considered whether the peek object should be read-only, but 
someone would say that they *want* be able to replace the next object to 
be yielded. Peeking into an exhausted iterable could raise instead of 
returning the sentinel, but I don't know if that would be more useful.

----------------
class lookahead():
     "Wrap iterator with lookahead to both peek and test exhausted"

     _NONE = object()
     def __init__(self, iterable):
         self._it = iter(iterable)
         self._set_peek()
     def __iter__(self):
         return self
     def __next__(self):
         ret = self.peek
         self._set_peek()
         return ret
     def _set_peek(self):
         try:
             self.peek = next(self._it)
         except StopIteration:
             self.peek = self._NONE
     def __bool__(self):
         return self.peek is not self._NONE

def test_lookahead():
     it = lookahead('abc')
     while it:
         a = it.peek
         b = next(it)
         print('next:', b, '; is peek:', a is b )

test_lookahead()
--------------------
 >>>
next: a ; is peek: True
next: b ; is peek: True
next: c ; is peek: True

-- 
Terry Jan Reedy




More information about the Python-ideas mailing list