How would one create an iterative function (or iterator object) in python?
Iterator objects in python conform to the iterator protocol, which basically means they provide two methods:
__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.
__next__() 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 self.high = high def __iter__(self): return self def __next__(self): # Python 2: def next(self) if self.current > self.high: raise StopIteration else: self.current += 1 return self.current - 1 for c in Counter(3, 8): 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, 8): 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:
nextin Python 2.x))
# generator def uc_gen(text): for char in text: yield char.upper() # generator expression def uc_genexp(text): return (char.upper() for char in text) # iterator protocol class uc_iter(): def __init__(self, text): self.text = text self.index = 0 def __iter__(self): return self def __next__(self): try: result = self.text[self.index].upper() except IndexError: raise StopIteration self.index += 1 return result # getitem method class uc_getitem(): def __init__(self, text): self.text = text def __getitem__(self, index): result = self.text[index].upper() return result
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, print
Which results in:
A B C D E A B C D E A B C D E A B C D E
The two generator types (
uc_genexp) cannot be
reversed(); the plain iterator (
uc_iter) would need the
__reversed__ magic method (which must return a new iterator that goes backwards); and the getitem iteratable (
uc_getitem) must have the
__len__ magic method:
# for uc_iter def __reversed__(self): return reversed(self.text) # 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