Build a basic Python iterator Build a basic Python iterator python python

Build a basic Python iterator


Iterator objects in python conform to the iterator protocol, which basically means they provide two methods: __iter__() and __next__().

  • The __iter__ returns the iterator object and is implicitly calledat the start of loops.

  • The __next__() method returns the next value and is implicitly called at each loop increment. This method raises a StopIteration exception when there are no more value to return, which is implicitly captured by looping constructs to stop iterating.

Here's a simple example of a counter:

class Counter:    def __init__(self, low, high):        self.current = low - 1        self.high = high    def __iter__(self):        return self    def __next__(self): # Python 2: def next(self)        self.current += 1        if self.current < self.high:            return self.current        raise StopIterationfor c in Counter(3, 9):    print(c)

This will print:

345678

This is easier to write using a generator, as covered in a previous answer:

def counter(low, high):    current = low    while current < high:        yield current        current += 1for c in counter(3, 9):    print(c)

The printed output will be the same. Under the hood, the generator object supports the iterator protocol and does something roughly similar to the class Counter.

David Mertz's article, Iterators and Simple Generators, is a pretty good introduction.


There are four ways to build an iterative function:

Examples:

# generatordef uc_gen(text):    for char in text.upper():        yield char# generator expressiondef uc_genexp(text):    return (char for char in text.upper())# iterator protocolclass uc_iter():    def __init__(self, text):        self.text = text.upper()        self.index = 0    def __iter__(self):        return self    def __next__(self):        try:            result = self.text[self.index]        except IndexError:            raise StopIteration        self.index += 1        return result# getitem methodclass uc_getitem():    def __init__(self, text):        self.text = text.upper()    def __getitem__(self, index):        return self.text[index]

To see all four methods in action:

for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:    for ch in iterator('abcde'):        print(ch, end=' ')    print()

Which results in:

A B C D EA B C D EA B C D EA B C D E

Note:

The two generator types (uc_gen and uc_genexp) cannot be reversed(); the plain iterator (uc_iter) would need the __reversed__ magic method (which, according to the docs, must return a new iterator, but returning self works (at least in CPython)); and the getitem iteratable (uc_getitem) must have the __len__ magic method:

    # for uc_iter we add __reversed__ and update __next__    def __reversed__(self):        self.index = -1        return self    def __next__(self):        try:            result = self.text[self.index]        except IndexError:            raise StopIteration        self.index += -1 if self.index < 0 else +1        return result    # for uc_getitem    def __len__(self)        return len(self.text)

To answer Colonel Panic's secondary question about an infinite lazily evaluated iterator, here are those examples, using each of the four methods above:

# generatordef even_gen():    result = 0    while True:        yield result        result += 2# generator expressiondef even_genexp():    return (num for num in even_gen())  # or even_iter or even_getitem                                        # not much value under these circumstances# iterator protocolclass even_iter():    def __init__(self):        self.value = 0    def __iter__(self):        return self    def __next__(self):        next_value = self.value        self.value += 2        return next_value# getitem methodclass even_getitem():    def __getitem__(self, index):        return index * 2import randomfor iterator in even_gen, even_genexp, even_iter, even_getitem:    limit = random.randint(15, 30)    count = 0    for even in iterator():        print even,        count += 1        if count >= limit:            break    print

Which results in (at least for my sample run):

0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 540 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 380 2 4 6 8 10 12 14 16 18 20 22 24 26 28 300 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32

How to choose which one to use? This is mostly a matter of taste. The two methods I see most often are generators and the iterator protocol, as well as a hybrid (__iter__ returning a generator).

Generator expressions are useful for replacing list comprehensions (they are lazy and so can save on resources).

If one needs compatibility with earlier Python 2.x versions use __getitem__.


I see some of you doing return self in __iter__. I just wanted to note that __iter__ itself can be a generator (thus removing the need for __next__ and raising StopIteration exceptions)

class range:  def __init__(self,a,b):    self.a = a    self.b = b  def __iter__(self):    i = self.a    while i < self.b:      yield i      i+=1

Of course here one might as well directly make a generator, but for more complex classes it can be useful.


matomo