Python Tutorial (20) - Iterators and Generators

Time: Column:Python views:252

Iterators

Iteration is one of Python's most powerful features, offering a way to access the elements of a collection.

An iterator is an object that remembers its position during traversal.

It begins by accessing the first element of the collection and moves forward until all elements are visited. Iterators can only move in one direction—forward.

Two basic methods define an iterator: iter() and next().

Strings, lists, or tuple objects can be used to create an iterator:

Example (Python 3.0+)

>>> my_list = [1, 2, 3, 4]
>>> it = iter(my_list)    # Creating an iterator object
>>> print(next(it))       # Output the next iterator element
1
>>> print(next(it))
2

Iterating with a for loop

You can also use a for loop to traverse through an iterator:

Example (Python 3.0+)

#!/usr/bin/python3
 
my_list = [1, 2, 3, 4]
it = iter(my_list)    # Creating an iterator object
for x in it:
    print(x, end=" ")

Output:

1 2 3 4

Using next()

You can also manually iterate through items using the next() function:

Example (Python 3.0+)

#!/usr/bin/python3
 
import sys         # Import sys module
 
my_list = [1, 2, 3, 4]
it = iter(my_list)    # Creating an iterator object
 
while True:
    try:
        print(next(it))
    except StopIteration:
        sys.exit()

Output:

1
2
3
4

Creating an Iterator

To use a class as an iterator, the class needs to implement two methods: __iter__() and __next__().

If you're familiar with object-oriented programming, you know classes have constructors. In Python, the constructor method is __init__(), which initializes the object when it's created.

The __iter__() method should return the iterator object itself and must implement the __next__() method. The __next__() method returns the next value from the iterator and raises a StopIteration exception to signal the end of the iteration.

Example: Creating an Iterator Class

The following example creates an iterator that returns numbers starting from 1, increasing by 1:

Example (Python 3.0+)

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    x = self.a
    self.a += 1
    return x
 
myclass = MyNumbers()
myiter = iter(myclass)
 
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

Output:

1
2
3
4
5

StopIteration Exception

The StopIteration exception signals the end of the iteration and prevents infinite loops. You can set the StopIteration exception after a certain number of iterations to stop the process.

Example: Stopping After 20 Iterations

Example (Python 3.0+)

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    if self.a <= 20:
      x = self.a
      self.a += 1
      return x
    else:
      raise StopIteration
 
myclass = MyNumbers()
myiter = iter(myclass)
 
for x in myiter:
  print(x)

Output:

1
2
3
...
19
20


Generators

In Python, a function that uses yield is called a generator.

The yield keyword defines a generator function that produces values one at a time during iteration, rather than returning them all at once.

Unlike normal functions, generators return an iterator, which can be used to iterate over the values produced by the generator function.

When the yield statement is executed, the function's state is "paused," and the expression following yield is returned as the current iteration's value. When the generator's next() method is called again or the generator is used in a for loop, the function resumes execution from where it last left off.

Example: Generator Function

def countdown(n):
    while n > 0:
        yield n
        n -= 1
 
# Creating a generator object
generator = countdown(5)
 
# Getting values from the generator
print(next(generator))  # Output: 5
print(next(generator))  # Output: 4
print(next(generator))  # Output: 3
 
# Iterating through the remaining generator values
for value in generator:
    print(value)  # Output: 2 1

The countdown function in this example is a generator function that yields countdown numbers from n to 1. You can retrieve values using next() or iterate over them with a for loop.

Generators have the advantage of producing values on demand, rather than computing and storing all values at once.


Fibonacci Sequence Generator

The following example demonstrates the use of yield to generate Fibonacci numbers:

Example (Python 3.0+)

#!/usr/bin/python3
 
import sys
 
def fibonacci(n):  # Generator function - Fibonacci
    a, b, counter = 0, 1, 0
    while True:
        if counter > n:
            return
        yield a
        a, b = b, a + b
        counter += 1

f = fibonacci(10)  # f is an iterator, returned by the generator
 
while True:
    try:
        print(next(f), end=" ")
    except StopIteration:
        sys.exit()


Output:

0 1 1 2 3 5 8 13 21 34 55