Overview
Python provides many built-in iterators, but there are situations where they may not match the behavior required by your program. In such cases, creating your own iterator allows you to decide exactly how values are generated and returned.
In this tutorial, you’ll learn how to build Python custom iterators from scratch. Starting with a simple iterator class, you’ll gradually implement the required methods, explore practical examples, and discover where custom iterators are most useful in real-world applications.
Introduction: Creating Custom Iterators in Python
Every iterator in Python follows the iterator protocol, but Python also allows developers to create their own iterators whenever custom iteration behavior is needed. Instead of relying only on built-in objects, you can define how values are produced, when iteration should stop, and what happens during each step.
Learning how to build Python custom iterators not only improves your understanding of iteration but also helps you write reusable and memory-efficient code for specialized tasks.
Why Create a Custom Iterator?
Built-in iterators work well for common collections such as lists, tuples, strings, and dictionaries. However, some programs require values to be generated according to custom rules that built-in iterators cannot provide.
For example, you might want to generate only even numbers, count backwards, produce values indefinitely, or process data one record at a time. A custom iterator gives you complete control over how this sequence is created.
Before You Begin
Before building your first custom iterator, make sure you’re familiar with the Python iterator protocol, including the __iter__() and __next__() methods, as well as the StopIteration exception.
If these concepts are new to you, we recommend reading the Python Iterator Protocol tutorial first, as this guide builds directly on those fundamentals.
Steps to Create a Custom Iterator
Creating an iterator is a straightforward process. Follow the steps below to build a fully functional custom iterator class.
Step 1: Create the Iterator Class
Begin by defining a class that will represent your iterator. This class will contain the data and methods needed to manage the iteration process.
Step 2: Initialize the Iterator State
Use the class constructor to initialize any variables required during iteration, such as the starting value, ending value, or current position.
Step 3: Implement __iter__()
Add the __iter__() method so your class can behave like an iterator. In most cases, this method simply returns the iterator object itself.
Step 4: Implement __next__()
Define the logic for producing the next value in the sequence. Each call to __next__() should return a single value while updating the iterator’s current state.
Step 5: Raise StopIteration
When there are no more values to produce, raise the StopIteration exception. This tells Python that the iteration has finished.
Step 6: Use the Custom Iterator
After implementing the required methods, your iterator can be used with a for loop or by calling the next() function manually.
Custom Iterator Examples
The following examples demonstrate different ways to create custom iterators. Each example introduces a new idea, helping you understand how iterator behavior can be customized for different situations.
Example 1: Create a Basic Counter Iterator
This example creates a simple custom iterator that returns numbers from 1 to 5 in sequence. It introduces the basic structure of a Python custom iterator and shows how values are generated one at a time during iteration.
class Counter:
def __init__(self):
self.current = 1
def __iter__(self):
return self
def __next__(self):
if self.current <= 5:
value = self.current
self.current += 1
return value
raise StopIteration
counter = Counter()
for number in counter:
print(number)
#Output
1
2
3
4
5
Explanation: The iterator starts at 1 and returns one number during each iteration. After reaching 5, it raises the StopIteration exception, signaling that there are no more values to retrieve. The for loop catches this exception automatically and stops without producing an error.
Example 2: Create a Limited Number Iterator
This example creates a custom iterator that returns numbers up to a specified limit. It introduces how a Python custom iterator can stop automatically after reaching a predefined value.
class LimitedCounter:
def __init__(self, limit):
self.current = 1
self.limit = limit
def __iter__(self):
return self
def __next__(self):
if self.current <= self.limit:
value = self.current
self.current += 1
return value
raise StopIteration
counter = LimitedCounter(7)
for number in counter:
print(number)
# Output
1
2
3
4
5
6
7
Explanation: Unlike the previous example, this iterator does not have a fixed stopping point. The value passed to the limit parameter determines the last number generated. Once the current value becomes greater than the specified limit, the StopIteration exception is raised, causing the for loop to end automatically.
Example 3: Create a Reverse Iterator
This example creates a custom iterator that returns elements in reverse order. It introduces how a Python custom iterator can control the direction of iteration.
class ReverseIterator:
def __init__(self, items):
self.items = items
self.index = len(items) - 1
def __iter__(self):
return self
def __next__(self):
if self.index >= 0:
value = self.items[self.index]
self.index -= 1
return value
raise StopIteration
fruits = ReverseIterator(["Apple", "Banana", "Mango"])
for fruit in fruits:
print(fruit)
#Output
Mango
Banana
Apple
Explanation: The iterator begins with the last element in the list instead of the first. After returning each value, it moves the index backward until all elements have been processed. When no items remain, the StopIteration exception ends the iteration automatically.
Example 4: Create an Even Number Iterator
This example creates a custom iterator that returns only even numbers. It introduces how a Python custom iterator can generate values based on specific conditions.
class EvenNumbers:
def __init__(self, limit):
self.current = 2
self.limit = limit
def __iter__(self):
return self
def __next__(self):
if self.current <= self.limit:
value = self.current
self.current += 2
return value
raise StopIteration
numbers = EvenNumbers(10)
for number in numbers:
print(number)
#Output
2
4
6
8
10
Explanation: Instead of returning every number in sequence, this iterator generates only even numbers. After returning a value, it increases the current number by 2 and continues until the specified limit is reached. Once the current value becomes greater than the specified limit, the StopIteration exception ends the iteration automatically.
Example 5: Create a Character Iterator for a String
This example creates a custom iterator that returns one character at a time from a string. It introduces how a Python custom iterator can traverse text sequentially.
class CharacterIterator:
def __init__(self, text):
self.text = text
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index < len(self.text):
character = self.text[self.index]
self.index += 1
return character
raise StopIteration
letters = CharacterIterator("Python")
for letter in letters:
print(letter)
# Output:
P
y
t
h
o
n
Explanation: Instead of returning the entire string at once, this iterator retrieves one character during each iteration. After returning a character, it moves to the next position in the string and continues until all characters have been processed. Once the end of the string is reached, the StopIteration exception ends the iteration automatically.
Example 6: Create an Infinite Iterator
This example creates a custom iterator that continues generating values without a predefined end. It introduces how a Python custom iterator can produce an endless sequence until the iteration is stopped manually.
class InfiniteCounter:
def __init__(self):
self.current = 1
def __iter__(self):
return self
def __next__(self):
value = self.current
self.current += 1
return value
counter = InfiniteCounter()
for number in counter:
if number > 5:
break
print(number)
# Output:
1
2
3
4
5
Explanation: Unlike the previous iterators, this iterator never raises StopIteration. Instead, it keeps generating the next number indefinitely. In this example, the break statement is used to stop the loop after printing the first five numbers, preventing an infinite loop.
Practical Use Cases of Custom Iterators
Below are some practical situations where a Python custom iterator can be useful:
- Processing large datasets: Retrieve one record at a time instead of loading the entire dataset into memory.
- Generating custom sequences: Create sequences such as even numbers, odd numbers, prime numbers, or Fibonacci numbers as needed.
- Traversing data in a custom order: Iterate through collections in reverse or follow a user-defined traversal pattern.
- Reading streaming data: Process values from log files, network connections, or sensors as they become available.
- Building reusable iteration logic: Encapsulate complex iteration behavior inside a class so it can be reused throughout a project.
Common Beginner Mistakes While Creating Custom Iterators
Below are some common mistakes to avoid when creating a Python custom iterator:
- Forgetting to return
selffrom__iter__(): If__iter__()does not return the iterator object, iteration will not work correctly. - Not raising
StopIteration: Every custom iterator should signal when no more values are available. Omitting this exception can result in an infinite loop. - Not updating the iterator state: Forgetting to modify the current position inside
__next__()causes the same value to be returned repeatedly. - Returning the wrong value: Ensure that
__next__()returns the intended value before updating the iterator state. - Modifying the iteration state incorrectly: Updating the counter or index in the wrong order can cause values to be skipped or repeated.
- Using a custom iterator when a generator is sufficient: If the iteration logic is simple, a generator can often provide the same result with much less code.
Best Practices for Creating Custom Iterators
Follow these best practices to build a reliable Python custom iterator:
- Keep the iterator focused: Design each iterator to perform one specific iteration task instead of handling multiple responsibilities.
- Always raise
StopIterationcorrectly: Signal the end of the iteration when no more values are available. - Update the iterator state carefully: Ensure the current position changes correctly after each value is returned.
- Choose meaningful attribute names: Use descriptive names such as
current,index, orlimitto improve readability. - Test different scenarios: Verify that the iterator works correctly with empty, small, and large inputs.
- Use generators for simple iteration: When the logic is straightforward, consider using a generator instead of creating a custom iterator class.
Key Takeaways: Creating Custom Iterators
Let’s quickly review the main concepts covered in this Creating Custom Iterators guide.
- Custom iterators allow you to define your own iteration behavior.
- A custom iterator must implement the
__iter__()and__next__()methods. - The
__iter__()method returns the iterator object, while__next__()returns one value at a time. - Raise
StopIterationto indicate that no more values are available. - Custom iterators can generate sequences, process data, or traverse collections in specialized ways.
- Following best practices helps create reliable, reusable, and easy-to-maintain iterator classes.