Python Iterators

Iterators : 

An iterator in python is an object that can be iterated upon. This means the iterator returns the data one at a time. __iter__( )  method is used for initializing the iterator object, and __next__( ) is used to iterate over the object. 

__iter__( ) and __next__( )  together called as iterator protocol. In python for loop internally creates an iterable object and calls the __next__( ) to get the elements one by one. 

If we can iterate over any object then it is called Iterable. In python strings, lists, tuples, sets, and dictionaries are iterable. 

NOTE: Everything is an object in python. Lists, Tuples, Sets, and Dictionaries all are objects of their respective classes. When we create any of them, an object of the particular class is created, and all the attributes and methods of the class can be used on this object. 

Iterating Through an Iterator : 

For an iterable,  let’s create an iterable object and use the next( ) function to iterate through it. 

NOTE : next(obj) is same as obj.__next__( )

Code:

Python Code

# declaring a iterable.
# Here iterable is string.
s = "tuf"

# intializing the iterator object.
iterator = iter(s)

# Iterating using the __next__()
# outputs t
print(iterator.__next__())

# outputs u
print(next(iterator))

# outputs f
print(iterator.__next__())

# This will raise error,because no items are left
print(iterator.__next__())

Output:

t
u
f

Traceback (most recent call last):
File “./prog.py”, line 19, in <module>
StopIteration

Working of for loop : 

In python for loop is used to iterate over the iterable like strings, Lists, Tuples, etc. For loop internally uses the iterators to iterate over the iterable.  Internally for loop creates an iterator object and calls the next() method to get the next element. When no elements are left in iterable, then it raises the StopIteration error then the loop will end. Let’s look at how for loop is implemented internally. 

For loop syntax what actually we use :

for element in iterable : 
       #something is done on element 

How for loop works internally:

# creating an iterator object of the iterable
iterator = iter(iterable)
while True:
    try:
        # getting the next element
        element = iterator.__next__()
        # doing something with the element
    except StopIteration:
        #if there is no next element, StopIteration error is raised
        # then break from loop
        break

It is clearing from the internal working of for loop, is that for loop is actually an infinite while loop.

Building Custom Iterators : 

We can build our own custom iterator. Building a custom iterator is easy in Python, we just need to take care of __iter__( ) and __next__( ) methods. The __iter__( ) method should return the iterator object. Some initializations can be performed in the __iter__( ) method if required. The __next__( ) method returns the next element in the iterable, If there are no elements then in further calls it should raise the StopIteration error. Let’s us build a custom iterator that gives the factorial of a number starting from 1 until the limit is reached in each iteration. 

Code:

Python Code

# Programm of custom iterator which prints the factorial of numbers till the limit number is reached.

class factorial:

    # constructor
    # Intializing the limit
    def __init__(self, limit):
        self.limit = limit

    # returns an iterator object when called.
    def __iter__(self):
        self.num = 1
        self.fact = 1
        return self

    # returns the factorial of next number
    # until the number <= limit.
    def __next__(self):

        # current number.
        curr_num = self.num

        # current number factorial
        curr_fact = self.fact

        # if current number <= limit
        if curr_num <= self.limit:

            # updating number for next iteration
            self.num = self.num + 1

            # updating factorial for next iteration
            self.fact = self.fact*self.num

            # returning curent number factorial
            return curr_fact

        # if number exceeds limit raise error
        else:
            raise StopIteration


# intialzing the object of the class factorial
fact = factorial(5)
# creating a iterable from the object
# here fact_iter is a iterable.
# which gives factorials of numbers,satisfying 1 <= number <= limit
fact_iter = iter(fact)
print(next(fact))
print(fact.__next__())
print(next(fact))
print(fact.__next__())
print(next(fact))
print(next(fact)) 

output:

1
2
6
24
120

Traceback (most recent call last):
File “./prog.py”, line 54, in <module>
File “./prog.py”, line 40, in next
StopIteration

In the above code fact is the object of the class factorial, and fact_iter is the iterable object. In the above code, we explicitly find out the iterable object and iterated it using the __next_( ) method. In each iteration, we got the factorial of numbers starting from 1. As the limit is 5 we got 1 to 5 numbers factorial. As the __next__( ) method is called even after reaching the limit, the StopIteration error is raised.

 We can also use for loop to iterate through it.  For loop internally calls the __iter__( ) and __next__( ) method . So we don’t need to create an iterable object and call the next() function over it. 

Code:

Python Code

# Programm of custom iterator which prints the factorial of numbers till the limit 
#number is reached.

class factorial:

    # constructor
    # Intializing the limit
    def __init__(self, limit):
        self.limit = limit

    # returns an iterator object when called.
    def __iter__(self):
        self.num = 1
        self.fact = 1
        return self

    # returns the factorial of next number
    # until the number <= limit.
    def __next__(self):

        # current number.
        curr_num = self.num

        # current number factorial
        curr_fact = self.fact

        # if current number <= limit
        if curr_num <= self.limit:

            # updating number for next iteration
            self.num = self.num + 1

            # updating factorial for next iteration
            self.fact = self.fact*self.num

            # returning curent number factorial
            return curr_fact

        # if number exceeds limit raise error
        else:
            raise StopIteration


# intialzing the object of the class factorial
fact = factorial(5)
for x in fact:
    print(x)

Output:

1
2
6
24
120

Here no error is raised because, for loop breaks when it encounters an error. So further __next__( ) method calls are not done. 

Special thanks to SaiSri Angajala for contributing to this article on takeUforward. If you also wish to share your knowledge with the takeUforward fam, please check out this articleIf you want to suggest any improvement/correction in this article please mail us at [email protected]