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

Ron Adam ron3200 at gmail.com
Mon Feb 25 16:29:52 CET 2013



On 02/24/2013 09:41 PM, Terry Reedy wrote:
> 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()


I think with a few small changes I would find it useful.

The key feature here is that the result is pre calculated and held until 
it's needed, rather than calculated when it's asked for.

You should catch any exception and hold that as well.  On the next .next() 
call, it should raise the exception if there was one, or emit the value.

I'm not sure if using the __bool__ attribute is the best choice.  I would 
prefer a .error flag, along with a .next_value attribute.  It would make 
the code using it easier to follow.

      it.error        <--    True if next(it) will raise an exception.
      it.next_value   <--    The next value, or the exception to raise.


Note that iterating a list of exceptions will still work.



About it.error.  If it was a concurrent version, then it.error could have 
three values.

      it.error == True   # Will raise an exception
      it.error == False  # Will not raise an exception
      it.error == None   # Still calculating


I wonder how this type of generator will behave with "yield from".  And if 
there would be any advantages for writing concurrent (or concurrent acting) 
code.


Of course you really need to think about weather or not this fits the 
problem being solved.

Cheers,
    Ron




More information about the Python-ideas mailing list