Explore programming tutorials, exercises, quizzes, and solutions!
Python Iterators and Generators Exercises
1/15
In Python, iterators and generators are tools that let you loop over data one item at a time without needing to load everything into memory at once. These are essential when dealing with large datasets or streams of data where memory efficiency is important.
An iterator is any object that implements the __iter__() and __next__() methods. It remembers its state and returns items one by one when next() is called, raising StopIteration when done.
A generator, on the other hand, is a special type of iterator that is defined using a function and the yield keyword. It simplifies the creation of iterators by managing the state internally and pausing/resuming execution automatically.
Consider the following question:
Which of the following best describes the difference between an iterator and a generator in Python?
Generators are a convenient way to create iterators. Instead of manually writing __iter__() and __next__() methods, you can define a generator function using yield. Each time the generator is resumed, it continues from where it left off, preserving state between calls.
While both iterators and generators allow you to traverse data lazily (one item at a time), generators offer more readable and maintainable code, especially when building custom iterable sequences. They're commonly used in pipelines, streaming APIs, and memory-sensitive applications.
What will the following code print?
def counter():
i = 0
while i < 3:
yield i
i += 1
gen = counter()
print(next(gen))
print(next(gen))
The generator counter yields values from 0 to 2. Each next() call resumes from where the generator left off, so the output will be 0 then 1.
The following class intends to implement an iterator, but throws an error during iteration. Identify the correction needed.
class ReverseList:
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
self.index -= 1
if self.index < 0:
raise StopIteration
return self.data[self.index]
for item in ReverseList([1, 2, 3]):
print(item)
To support iteration, the class must implement __iter__() and __next__() methods. Python looks for __next__() during iteration, not next().
Which of the following generator functions creates an infinite sequence of even numbers starting from 0?
Only option2 creates an infinite generator. The while True: loop continues forever, yielding the next even number by incrementing i by 2.
Which of the following generator functions mimics the behavior of the built-in enumerate() function?
option1 explicitly creates a generator using yield, manually mimicking enumerate() by pairing index and item during iteration.
Review the following generator function and determine what it yields:
def pairwise_sum(seq):
for i in range(len(seq) - 1):
yield seq[i] + seq[i + 1]
sums = list(pairwise_sum([10, 20, 30, 40]))
What is the value of sums?
The generator yields the sum of each pair of adjacent elements:
10+20=30, 20+30=50, 30+40=70 → resulting in [30, 50, 70].
You’re designing a data pipeline that processes streamed log lines. To avoid memory issues, you decide to implement it as a generator. Here's a partially completed function:
def log_stream(file_path):
with open(file_path) as f:
_____
Which of the following best completes the function so it yields lines one by one?
Using yield inside a loop allows the function to act as a generator, emitting lines one at a time without loading the entire file into memory.
You're working on a generator that yields the square of each number from a given list, but you want the function to stop if a square exceeds 100. Consider this function:
def limited_squares(nums):
for num in nums:
square = num ** 2
if square > 100:
break
yield square
What is the result of running list(limited_squares([5, 8, 11]))?
5² = 25, 8² = 64 → both are yielded.
11² = 121 → exceeds 100, so iteration stops before yielding it.
You're simplifying a nested generator using yield from. Here's an existing setup:
Iterators are exhausted after a full pass. result1 consumes all items. When calling list(it) again for result2, the iterator is empty.
You're building a custom iterator that returns characters of a string in reverse. Here's the incomplete implementation:
class ReverseString:
def __init__(self, s):
self.s = s
self.index = len(s)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index -= 1
return self.s[self.index]
What will be the output of this code?
rev = ReverseString("abc")
print(list(rev))
The custom iterator decrements the index from the end of the string, yielding characters in reverse order: 'c', 'b', 'a'.
You want to create a generator pipeline that filters even numbers and then doubles them. Which of the following accomplishes this correctly?
Only option1 uses yield correctly in a loop to lazily emit filtered and transformed values, suitable for a memory-efficient generator pipeline.
You’re implementing a generator to track running totals as values are received. Here’s the partial function:
def running_total():
total = 0
while True:
value = yield total
total += value
What is the correct way to use this generator to send values into it?
Before using send(), the generator must be advanced to the first yield using next(). Otherwise, send() will raise a TypeError.
You’re debugging a case where a generator expression is used inside a list comprehension. Examine the code below:
result = [x for x in (i for i in range(3))]
What will be stored in result?
The inner generator expression (i for i in range(3)) produces values 0, 1, 2, which are unpacked one-by-one by the outer list comprehension.
You want to design a function that accepts multiple iterables and yields their elements one after another (like itertools.chain). Which implementation is valid?
Option 2 correctly iterates through each iterable and then through each element inside it, yielding elements one-by-one like a flattened stream.
Practicing Python Iterators and Generators? Don’t forget to test yourself later in
our
Python Quiz.
About This Exercise: Python – Iterators and Generators
Welcome to the Python Iterators and Generators exercises — a comprehensive set of challenges designed to help you master two fundamental concepts that make Python powerful and efficient when working with sequences and data streams. Iterators provide a way to traverse through all the elements of a collection, while generators offer a memory-efficient method to produce sequences on the fly without storing the entire sequence in memory.
In this section, you will learn how to create and use iterators by implementing the iterator protocol with the __iter__() and __next__() methods. These exercises guide you through writing custom iterator classes and understanding how built-in Python iterators work under the hood. You’ll also explore practical examples of using iterators to loop through data collections seamlessly.
Generators simplify iterator creation by allowing you to write functions that yield values one at a time, pausing and resuming their state between each yield. These exercises will help you master generator functions and generator expressions, demonstrating their advantages for handling large datasets, streams, and pipelines efficiently without exhausting system resources.
Understanding iterators and generators is essential for Python developers working in data processing, web scraping, real-time data analysis, and any application where efficient memory use and lazy evaluation are important. These concepts also prepare you for advanced Python topics such as coroutines and asynchronous programming.
Alongside practical coding problems, this section emphasizes best practices for writing clean and readable iterator and generator code, including when to choose generators over lists, handling exceptions within generators, and composing generator pipelines. This balanced approach will deepen your Python expertise and prepare you for technical interviews focused on iteration and data handling.
We recommend supplementing these exercises with related topics like list comprehensions, lambda functions, and the itertools module to build a strong foundation in Python’s data manipulation capabilities. Quizzes and multiple-choice questions on iterators and generators are also available to reinforce your learning.
Start practicing the Python Iterators and Generators exercises today to unlock efficient and elegant ways of handling sequences and data streams. With regular practice, you’ll gain confidence in writing Python code that is both powerful and resource-friendly.