4. Iterators & Generators
Iterators are one of Python's most powerful features.
At a high level, you might view iteration simply as a way to process items in a sequence,
but there is much more than this such as creating your own iterator object, applying useful
iterator patterns in itertools
module, make generator functions etc.
4.1: Manually consuming an iterator
problem
You wan tto consume items in an iterable but for whatever reasons you can't
or don't want to use a for
loop
Solution
To manually consume the iterable, use the next()
function and write
code to catch the StopIteration
exception as shown below.
1 2 3 4 5 6 7 |
|
Normally
StopIteration
exception is used to signal the end of the sequence.
But, if you are using
next
, you can instruct next()
to return a terminating value
like None
as shown below.
1 2 3 4 5 6 7 |
|
Discussion
In most cases the for
is used to consume the iterable.
However every now and then a problem statement calls for precise control over
the underlying mechanism. The below example explains what happens underneath
the iteration protocol.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
4.2: Delegating Iteration
Problem
You have built a custom container object that internallt holds a list
or tuple
or other iterables. You want the iteration to work with this custom container.
Solution
Typically all you need to do is to define an internal iter()
method that delegates iteration
to internally held container as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
1 2 |
|
__iter__()
method simply forwards the iteration request
to the internally held self._children
attribute.
Discussion
Python's iterator protocol requires the __iter__()
method to return a special
iterator object that implements a __next__()
method which actually carries out
the actual iteration.
In the above example, we are delegating the implemeation of __next__()
method
by returning a list (iter(self._children)
) which internally implements __iter__()
.
Calling the
iter(x)
function internally calls x.__iter__()
.
Note that this behaviour is similar to len(seq)
and seq.__len__()