Iterator Special Method Names
An iterator is the object responsible for controlling iteration
through a collection of objects or range of values. The
for
statement works by calling the
next
method of an iterator until the iterator
raises an exception. The
yield
statement makes a
function (or method) into an iterator by implicitly creating an object
with a next
method. We looked at this closely
in Chapter 18, Generators and the
yield
Statement
. The techniques there (principally,
using the
yield
statement) are somewhat simpler than
creating an explicit iterator object.
Generally, we provide a sequence object to the
for
statement. The sequence object responds to the
for
statement's request by creating the required
iterator. Clearly, a statement like
for
var
in
object
statement evaluates the
object
(.iter) method
function to get the necessary iterator object.
The built-in sequence types (list
,
tuple
, string
) all produce
iterator objects for use by the
for
statement. The
set
and frozenset
classes
also produces an iterator. In the case of a mapping, there are several
choices for the target of the iteration: the iterator could iterate over
the keys, the values or the items (which are
(
key
,
value
)
pairs).
In addition to defining ordinary generator methods by using the
yield
statement, your classes can also produce
iterator objects. This can make a program slightly simpler to read by
assuring that loops are simple, obvious
for
statements.
Creating an Iterator. Generally, an iterator is an object that helps a program with
with a more complex container. Consequently, the container will often
contain a factory method which creates iterators. The special method
__iter__
usually handles this in a container.
The for statement uses the iter
built-in
function. The iter
function looks for the
__iter__
method.
An iterator object is created by a collection object when
requested by the
for
statement. To make your
collection play well with the
for
statement,
implement the following method.
-
__iter__
(
self
)
→ Iterator
-
Returns an iterator in response to the
iter
function. The iter
function is implicitly evaluated by a
for
statement.
An iterator controls the operation of the
for
statement, so it is clearly stateful. In addition to at least one
internal variable, an iterator is usually created with a reference to
the more complex object with which it works.
When we evaluate iter(
someList
)
, we get an iterator object ready to be used with a
for
statement. The iter function uses
__iter__
method function of
someList
. The __iter__
function creates the object to be used as an iterator.
A complex object will usually provide it's self
variable to each iterator that it creates.
Methods of an Iterator. An iterator object has a simple interface definition: it
provides a next
method, which either returns the
next value or raises the StopIteration
exception. Further, an iterator needs an
__init__
method which will accept the complex
object over which the iterator works, and allows the iterator to
create appropriate initial values.
-
__init__
(
self
,
complexObject
)
-
This will initialize the iterator. Generally, a complex
object will create an iterator from it's own
__iter__
method, providing it's
self
variable as the argument. It will do
something like this: return MyIterator( self
)
.
-
next
(
self
) →
Object
-
This will advance the iterator to the next value or element.
If there is no next value, it will raise the
StopIteration
exception. If there
is a next value, it will return this value.
There's little more than these two methods to an iterator. Often
an iterator will also provide a definition of the __iter__ special
method name. This will simply return the iterator. This prevents small
problems with redundant calls to the iter
built-in
function.
Example: Non-Zero Iterator. In the following example classes, we'll create a class which
wraps a list
and provides and a specialized
iterator that yields only non-zero values of the collection.
class DataSamples( object ):
def __init__( self, aList=None ):
self.values= aList or []
def __iter__( self ):
return NonZeroIter( self )
def __len__( self ):
return len( self.values )
def __getitem__( self, index ):
return self.values[index]
|
When we initialize a DataSamples
instance, we save any provided sequence of values. This class
behaves like a collection. We haven't provided all of the methods,
however, in order to keep the example short. Clearly, to be
list -like, we'll need to provide an
append method.
|
|
When we evaluate the iter function for a
DataSamples object, the
DataSamples object will create a new,
initialized NonZeroIter . Note that we provide
the DataSamples object to the new
NonZeroIter , this allows the iterator to
process the collection properly.
|
class NonZeroIter( object ):
def __init__( self, aDataSamples ):
self.ds= aDataSamples
self.pos= -1
def next( self ):
while self.pos+1 != len(self.ds) and self.ds[self.pos+1] == 0:
self.pos += 1
if self.pos+1 == len( self.ds ):
raise StopIteration
self.pos += 1
return self.ds[self.pos]
def __iter__( self ):
return self
|
When initialized, the NonZeroIter saves
the collection that it works with. It also sets it's current state;
in this instance, we have pos set to
-1 , just prior to the element we'll
return.
|
|
The next function of the iterator locates
the next non-zero value. If there is no next value or no next
non-zero value, it raises
StopIteration to notify the
for
statement. Otherwise, it returns the next
non-zero value. It updates its state to reflect the value just
returned.
|
|
The __iter__ function of the iterator
typically returns self .
|
We can make use of this iterator as follows.
ds = DataSamples( [0,1,2,0,3,0] )
for value in ds:
print value
The
for
statement calls iter(ds)
implicitly, which calls ds.__iter__()
, which creates the
NonZeroIter
instance. The
for
statement then calls the next
method of this
iterator object to get the non-zero values from the
DataSamples
object. When the iterator finally
raises the StopIteration
exception, the
for
statement finishes normally.