The Python iterator interface is defined using a method called __next__ that returns the next element of some underlying sequential series that it represents. In response to invoking __next__, an iterator can perform arbitrary computation in order to either retrieve or compute the next element. Calls to __next__ make a mutating change to the iterator: they advance the position of the iterator. Hence, multiple calls to __next__ will return sequential elements of an underlying series. Python signals that the end of an underlying series has been reached by raising a StopIteration exception during a call to __next__.
The LetterIter class below iterates over an underlying series of letters from some start letter up to but not including some end letter. The instance attribute next_letter stores the next letter to be returned. The __next__ method returns this letter and uses it to compute a new next_letter.
>>> class LetterIter:
"""An iterator over letters of the alphabet in ASCII order."""
def __init__(self, start='a', end='e'):
self.next_letter = start
self.end = end
def __next__(self):
if self.next_letter == self.end:
raise StopIteration
letter = self.next_letter
self.next_letter = chr(ord(letter)+1)
return letter
Using this class, we can access letters in sequence using either the __next__ method or the built-in next function, which invokes __next__ on its argument.
>>> letter_iter = LetterIter()
>>> letter_iter.__next__()
'a'
>>> letter_iter.__next__()
'b'
>>> next(letter_iter)
'c'
>>> letter_iter.__next__()
'd'
>>> letter_iter.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 12, in next
StopIteration
Iterators are mutable: they track the position in some underlying sequence of values as they progress. When the end is reached, the iterator is used up. A LetterIter instance can only be iterated through once. After its __next__() method raises a StopIteration exception, it continues to do so from then on. Typically, an iterator is not reset; instead a new instance is created to start a new iteration.
Iterators also allow us to represent infinite series by implementing a __next__ method that never raises a StopIteration exception. For example, the Positives class below iterates over the infinite series of positive integers. The built-in next function in Python invokes the __next__ method on its argument.
>>> class Positives:
def __init__(self):
self.next_positive = 1;
def __next__(self):
result = self.next_positive
self.next_positive += 1
return result
>>> p = Positives()
>>> next(p)
1
>>> next(p)
2
>>> next(p)
3