Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build a basic iterator?

How would one create an iterative function (or iterator object) in python?

like image 720
akdom Avatar asked Aug 21 '08 00:08

akdom


People also ask

How do I make an iterator?

An iterator can be created from an iterable by using the function iter(). Thus, when we pass this tuple to an iter() function, we will get an iterator. Actually, this is possible because the class of an object has a method __iter__, which returns an iterator.

Are simple way of creating iterators?

The easiest way to create an iterator is by making a generator function, so that's just what we did. We stuck yield in our __iter__ to make it into a generator function and now our Point class can be looped over, just like any other iterable.

How do you make an iterator in Python?

To create an object/class as an iterator you have to implement the methods __iter__() and __next__() to your object. As you have learned in the Python Classes/Objects chapter, all classes have a function called __init__() , which allows you to do some initializing when the object is being created.

How do you make an iterator class?

To create an Iterator class we need to override __next__() function inside our class i.e. __next__() function should be implemented in such a way that every time we call the function it should return the next element of the associated Iterable class. If there are no more elements then it should raise StopIteration.


2 Answers

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 called at 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 StopIteration   for c in Counter(3, 9):     print(c) 

This will print:

3 4 5 6 7 8 

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 += 1  for 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.

like image 71
ars Avatar answered Oct 07 '22 16:10

ars


There are four ways to build an iterative function:

  • create a generator (uses the yield keyword)
  • use a generator expression (genexp)
  • create an iterator (defines __iter__ and __next__ (or next in Python 2.x))
  • create a class that Python can iterate over on its own (defines __getitem__)

Examples:

# generator def uc_gen(text):     for char in text.upper():         yield char  # generator expression def uc_genexp(text):     return (char for char in text.upper())  # iterator protocol class 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 method class 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 E A B C D E A B C D E A 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:

# generator def even_gen():     result = 0     while True:         yield result         result += 2   # generator expression def even_genexp():     return (num for num in even_gen())  # or even_iter or even_getitem                                         # not much value under these circumstances  # iterator protocol class 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 method class even_getitem():     def __getitem__(self, index):         return index * 2  import random for 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 54 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 0 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__.

like image 35
Ethan Furman Avatar answered Oct 07 '22 16:10

Ethan Furman