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]
1

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.

2

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
1

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.

2

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.

3

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.