Python - Iterable Objects: __iter__ and __next__

Introduction

In the __iter__ scheme, classes implement user-defined iterables by simply implementing the iteration protocol.

The following code defines a user-defined iterable that generates squares on demand.

Demo

class Squares: 
   def __init__(self, start, stop):    # Save state when created 
       self.value = start - 1 #  w ww .  j a  va  2s  . c  o m
       self.stop  = stop 
   def __iter__(self):                 # Get iterator object on iter 
       return self 
   def __next__(self):                 # Return a square on each iteration 
       if self.value == self.stop:     # Also called by next built-in 
           raise StopIteration 
       self.value += 1 
       return self.value ** 2 

for i in Squares(1, 5):             # for calls iter, which calls __iter__ 
   print(i, end=' ')               # Each iteration calls __next__

Result

Here, the iterator object returned by __iter__ is the instance self, because the __next__ method is part of this class itself.

Manual iterations work the same on user-defined iterables as they do on built-in types as well:

Demo

class Squares: 
   def __init__(self, start, stop):    # Save state when created 
       self.value = start - 1 #  w  w  w .  j  a  va 2s. c  om
       self.stop  = stop 
   def __iter__(self):                 # Get iterator object on iter 
       return self 
   def __next__(self):                 # Return a square on each iteration 
       if self.value == self.stop:     # Also called by next built-in 
           raise StopIteration 
       self.value += 1 
       return self.value ** 2 

X = Squares(1, 5)                   # Iterate manually: what loops do 
I = iter(X)                         # iter calls __iter__ 
print( next(I) )                             # next calls __next__ (in 3.X) 
print( next(I) )
print( next(I) ) 
print( next(I) )                            # Can catch this in try statement 

X = Squares(1, 5) 
print( X[1] )
list(X)[1]

Result