has_next in Python iterators?

Viewed 200651

Haven't Python iterators got a has_next method?

18 Answers

No, there is no such method. The end of iteration is indicated by an exception. See the documentation.

If you really need a has-next functionality, it's easy to obtain it with a little wrapper class. For example:

class hn_wrapper(object):
  def __init__(self, it):
    self.it = iter(it)
    self._hasnext = None
  def __iter__(self): return self
  def next(self):
    if self._hasnext:
      result = self._thenext
    else:
      result = next(self.it)
    self._hasnext = None
    return result
  def hasnext(self):
    if self._hasnext is None:
      try: self._thenext = next(self.it)
      except StopIteration: self._hasnext = False
      else: self._hasnext = True
    return self._hasnext

now something like

x = hn_wrapper('ciao')
while x.hasnext(): print next(x)

emits

c
i
a
o

as required.

Note that the use of next(sel.it) as a built-in requires Python 2.6 or better; if you're using an older version of Python, use self.it.next() instead (and similarly for next(x) in the example usage). [[You might reasonably think this note is redundant, since Python 2.6 has been around for over a year now -- but more often than not when I use Python 2.6 features in a response, some commenter or other feels duty-bound to point out that they are 2.6 features, thus I'm trying to forestall such comments for once;-)]]

===

For Python3, you would make the following changes:

from collections.abc import Iterator  # since python 3.3 Iterator is here

class hn_wrapper(Iterator):  # need to subclass Iterator rather than object
  def __init__(self, it):
    self.it = iter(it)
    self._hasnext = None
    
  def __iter__(self): 
    return self
  
  def __next__(self):        # __next__ vs next in python 2
    if self._hasnext:
      result = self._thenext
    else:
      result = next(self.it)
    self._hasnext = None
    return result
  
  def hasnext(self):
    if self._hasnext is None:
      try: 
        self._thenext = next(self.it)
      except StopIteration: 
        self._hasnext = False
      else: self._hasnext = True
    return self._hasnext

In addition to all the mentions of StopIteration, the Python "for" loop simply does what you want:

>>> it = iter("hello")
>>> for i in it:
...     print i
...
h
e
l
l
o

Try the __length_hint__() method from any iterator object:

iter(...).__length_hint__() > 0

You can tee the iterator using, itertools.tee, and check for StopIteration on the teed iterator.

No, there is no such method. The end of iteration is indicated by a StopIteration (more on that here).


This follows the python principle EAFP (easier to ask for forgiveness than permission). A has_next method would follow the principle of LBYL (look before you leap) and contradicts this core python principle.

This interesting article explains the two concepts in more detail.

Suggested way is StopIteration. Please see Fibonacci example from tutorialspoint

#!usr/bin/python3

import sys
def fibonacci(n): #generator function
   a, b, counter = 0, 1, 0
   while True:
      if (counter > n): 
         return
      yield a
      a, b = b, a + b
      counter += 1
f = fibonacci(5) #f is iterator object

while True:
   try:
      print (next(f), end=" ")
   except StopIteration:
      sys.exit()

It is also possible to implement a helper generator that wraps any iterator and answers question if it has next value:

Try it online!

def has_next(it):
    first = True
    for e in it:
        if not first:
            yield True, prev
        else:
            first = False
        prev = e
    if not first:
        yield False, prev

for has_next_, e in has_next(range(4)):
    print(has_next_, e)

Which outputs:

True 0
True 1
True 2
False 3

The main and probably only drawback of this method is that it reads ahead one more element, for most of tasks it is totally alright, but for some tasks it may be disallowed, especially if user of has_next() is not aware of this read-ahead logic and may missuse it.

Code above works for infinite iterators too.

Actually for all cases that I ever programmed such kind of has_next() was totally enough and didn't cause any problems and in fact was very helpful. You just have to be aware of its read-ahead logic.

Maybe it's just me, but while I like https://stackoverflow.com/users/95810/alex-martelli 's answer, I find this a bit easier to read:

from collections.abc import Iterator  # since python 3.3 Iterator is here

class MyIterator(Iterator):  # need to subclass Iterator rather than object
  def __init__(self, it):
    self._iter = iter(it)
    self._sentinel = object()
    self._next = next(self._iter, self._sentinel)
    
  def __iter__(self): 
    return self
  
  def __next__(self):        # __next__ vs next in python 2
    if not self.has_next():
      next(self._iter)  # raises StopIteration

    val = self._next
    self._next = next(self._iter, self._sentinel)
    return val
  
  def has_next(self):
    return self._next is not self._sentinel

The way has solved it based on handling the "StopIteration" execption is pretty straightforward in order to read all iterations :

    end_cursor = False
    while not end_cursor:
        try:
            print(cursor.next())
        except StopIteration:
            print('end loop')
            end_cursor = True
        except:
            print('other exceptions to manage')
            end_cursor = True

I think there are valid use cases for when you may want some sort of has_next functionality, in which case you should decorate an iterator with a has_next defined.

Combining concepts from the answers to this question here is my implementation of that which feels like a nice concise solution to me (python 3.9):

_EMPTY_BUF = object()


class BufferedIterator(Iterator[_T]):
    def __init__(self, real_it: Iterator[_T]):
        self._real_it = real_it
        self._buf = next(self._real_it, _EMPTY_BUF)

    def has_next(self):
        return self._buf is not _EMPTY_BUF

    def __next__(self) -> _T_co:
        v = self._buf
        self._buf = next(self._real_it, _EMPTY_BUF)
        if v is _EMPTY_BUF:
            raise StopIteration()
        return v

The main difference is that has_next is just a boolean expression, and also handles iterators with None values.

Added this to a gist here with tests and example usage.

very interesting question, but this "hasnext" design had been put into leetcode: https://leetcode.com/problems/iterator-for-combination/

here is my implementation:

class CombinationIterator:

def __init__(self, characters: str, combinationLength: int):
    from itertools import combinations
    from collections import deque
    self.iter = combinations(characters, combinationLength)
    self.res = deque()


def next(self) -> str:
    if len(self.res) == 0:
        return ''.join(next(self.iter))
    else:
        return ''.join(self.res.pop())


def hasNext(self) -> bool:
    try:
        self.res.insert(0, next(self.iter))
        return True
    except:
        return len(self.res) > 0
Related