Iterables, Iterators, and Generators in Python

In Python, iterables, iterators, and generators provide methods for generating data collections and for ordered traversal of data. If the amount of data generated is relatively small, it is recommended to use iterables; if the amount of data generated is large, it is recommended to use iterators or generators. Since the implementation of generators is usually simpler, it is advisable to use generators whenever possible instead of iterators.

This article mainly introduces the following topics:

  • Iterables

  • Iterators

  • Custom Iterables and Iterators

  • Generators

Iterables

An iterable is an object that implements the __iter__() method. Common iterables include lists, tuples, strings, dictionaries, and sets. The characteristics of iterables are as follows:

  • Can be accessed via a for loop, but cannot be accessed via next().

Example:

# Can be accessed via a for loop
my_list = list(range(6))
for elem in my_list:
    print(elem)
# The following code will raise an error because my_list is not an iterator and cannot be accessed via next()
# print(next(my_list))
  • Can be traversed multiple times.

Example:

# Can be traversed multiple times
my_list = list(range(6))
for elem in my_list:
    print(elem)  # This line will execute
for elem in my_list:
    print(elem)  # This line will execute
  • Generates all data at once.

  • Print results generally show all elements.

Example:

my_list = list(range(6))
print(my_list)  # Output: [0, 1, 2, 3, 4, 5]
  • Can be converted to an iterator using iter().

Iterators

An iterator is an object that implements both the __iter__() and __next__() methods. Built-in functions in Python such as enumerate(), zip(), map(), and filter() return iterators. The characteristics of iterators are as follows:

  • Can be accessed via a for loop and next(); next() is a built-in function in Python used to get the next element of the iterator.

Example:

my_list = ['one', 'two', 'three']
my_iterator = enumerate(my_list, start=1)
# Output: (1, 'one')
print(next(my_iterator))
# Output:
# (2, 'two')
# (3, 'three')
for elem in my_iterator:
    print(elem)
  • Can only be traversed in one direction and can only be traversed once.

Example:

my_list = ['one', 'two', 'three']
my_iterator = enumerate(my_list, start=1)
for elem in my_iterator:
    print(elem)  # This line will execute
for elem in my_iterator:
    print(elem)  # This line will not execute

In addition to traversing iterators using a for loop, many built-in functions (such as max(), min(), etc.) also traverse iterators. After traversing, there will be no return results if you try to traverse the iterator again.

Example:

my_list = ['one', 'two', 'three']
my_iterator = enumerate(my_list, start=1)
print(max(my_iterator))  # Output: (3, 'three')
for elem in my_iterator:
    print(elem)  # This line will not execute because max() has already traversed my_iterator once
  • Generates data on demand, suitable for scenarios that produce large amounts of data.

  • Print results generally show the address of the object.

Example:

my_list = ['one', 'two', 'three']
my_iterator = enumerate(my_list, start=1)
# Output similar to <enumerate object at 0x00000299B14B0680>
print(my_iterator)
  • Can be converted to an iterable using methods like list().

Custom Iterables and Iterators

The requirement is as follows: to generate integers between [begin, stop), we will define a custom iterable MyIterable and an iterator MyIterator.

Example:

class MyIterator:
    """
    Custom iterator
    """
    def __init__(self, begin, end):
        self.begin = begin
        self.end = end
        self.index = begin
    def __iter__(self):
        return self
    def __next__(self):
        if self.index < self.end:
            result = self.index
            self.index += 1
            return result
        else:
            raise StopIteration
class MyIterable:
    """
    Custom iterable
    """
    def __init__(self, begin, end):
        self.begin = begin
        self.end = end
    def __iter__(self):
        # Return an iterator object
        return MyIterator(self.begin, self.end)
    def __repr__(self):
        return str(list(self))
my_iterator = MyIterator(1, 3)
# Output:
# 1
# 2
for elem in my_iterator:
    print(elem)
my_iterable = MyIterable(1, 3)
# Output:
# 1
# 2
for elem in my_iterable:
    print(elem)
# Output: [1, 2]
print(my_iterable)

Generators

A generator is a special type of iterator that generates values on demand, rather than generating all values at once.

Generators do not need to implement __iter__() and __next__() methods; instead, they are implemented using “function + yield” which makes the implementation simpler.Each time yield is called, the function pauses and returns a value; the next time it is called, it continues execution from where it paused.

Example:

# Generate integers in the range [begin, end)
def generate(begin, end):
    for i in range(begin, end):
        yield i
my_generator = generate(6, 9)
# Output: 6
print(next(my_generator))
# Output:
# 7
# 8
for elem in my_generator:
    print(elem)
# Output: <class 'generator'>
print(type(my_generator))

In the above example, yield i means:use the yield keyword to return the current value i and pause the function’s execution; the next time it is called, the function will continue execution from after the yield statement.

Leave a Comment