Creating an Iterator in Python
An "iterable" object in Python is one that we can iterate on it using a for loop. For example a list is "iterable" because we can iterate over its elements:
examples/python/iterate_on_list.py
from __future__ import print_function names = ['Foo', 'Bar', 'Zorg'] for name in names: print(name)
to see:
Foo Bar Zorg
A string is also iterable because we can iterate over its characters:
examples/python/iterate_on_string.py
from __future__ import print_function name = 'Foo Bar' for c in name: print(c)
F o oB a r
A much more interesting example might be the use of xrange that creates an iterable object without taking up precious memory.
examples/python/iterate_on_xrange.py
from __future__ import print_function import sys r = xrange(10000) for v in r: pass print(sys.getsizeof(r)) # 40
See range vs. xrange for a longer discussion on the topic.
Why to create your own iterator?
Before showing how to create your own iterator, let's try to figure out why and when would we want to do that?
In all the following case you could achieve the same result without using a real iterator object, but creating an iterator object will allow us to use a simple for-loop which will make our code look more similar to other code written in Python.
One of the use-cases is when you would like to go over an infinite series of values up to a point that is determined only later, probably by some external force. For example take the well known case of the Fibonacci series. You might want to look at each element of the series and stop at the first element that can be divided by 17.
That case can already benefit from an iterator, but what if you work with a bunch of people researching the Fibonacci series. Each one of them will want to go over the Fibonacci series and stop at some condition. We just don't know what condition will be.
So we can create an iterator object that will be able to iterate over the Fibonacci numbers until the code of the researcher says it to stop or until power runs out of the computer.
Create an iterable
An iterable object can be created by any class. It only needs 3 things: 2 required and one optional.
The two required are:
- A method called __iter__ that will return the instance object. A very simple piece of code.
- A method called next that will return the next value of the iterable.
These two will create an unbounded or unlimited iterator.
Optionally you might want your iterator to stop at a certain point by itself. You can achieve that by having the next method raise a StopIteration exception.
Finate Iterator
In this example the constructor of the Fibonacci class is expected to receive a number, the number of elements we would like to iterate over. It does not calculate all the numbers at once, instead it computes the next element every time the next method is called which is done internally by the for loop.
When the iteration counter reaches the limit originally provided, itraises a StopIteration exception which is caught by the for loop (again without us doing anything special) and terminates the for-loop.
examples/python/fibonacci_finate_iterator.py
#!/usr/bin/env python from __future__ import print_function class Fibonacci(object): def __init__(self, limit): self.limit = limit self.count = 0 self.values = [] def __iter__(self): return self def next(self): self.count += 1 if self.count > self.limit: raise StopIteration if len(self.values) < 2: self.values.append(1) else: self.values = [self.values[-1], self.values[-1] + self.values[-2]] return self.values[-1] for f in Fibonacci(5): print(f) if f % 17 == 0: print('found') break print('-----') for f in Fibonacci(15): print(f) if f % 17 == 0: print('found') break
Infinite Iterator
In the next solution we don't need to provide a limit, our iterator never raises an exception, and therefor it never stops by itself. We assume that the user who uses this iterafor will have some code in place that will stop the iteration by calling break on some condition. This condition can be related to the series itself, (e.g. we have iterated over 100 elements), or it can be totally unrelated. (e.g. some user input. Elapsed time. Day of the week. Etc.)
examples/python/fibonacci_iterator.py
#!/usr/bin/env python from __future__ import print_function class Fibonacci(object): def __init__(self): self.values = [] def __iter__(self): return self def next(self): if len(self.values) < 2: self.values.append(1) else: self.values = [self.values[-1], self.values[-1] + self.values[-2]] return self.values[-1] for f in Fibonacci(): if f % 17 == 0: print(f) break if f > 10000: break
Comments
Very nice. Thank you!
%> python3 fibonacci_finate_iterator.py TypeError: iter() returned non-iterator of type 'Fibonacci'
Published on 2015-07-21