Python __iter__ Method

Complete guide to Python's __iter__ method covering iteration protocols, custom iterators, and practical examples.

Python __iter__ Method

Python iter Method

Last modified April 8, 2025

This comprehensive guide explores Python’s iter method, the special method that makes objects iterable. We’ll cover iteration protocols, custom iterators, generator functions, and practical examples.

Basic Definitions

The iter method returns an iterator object that implements the iteration protocol. It is called when an object needs to be iterated over, such as in a for loop.

Key characteristics: it must return an iterator object (which implements next), enables iteration over container objects, and works with Python’s built-in iter() function.

Basic iter Implementation

Here’s a simple implementation showing how iter makes a class iterable. The example demonstrates the minimal requirements for iteration.

basic_iter.py

class MyIterable: def init(self, data): self.data = data

def __iter__(self):
    return iter(self.data)

items = MyIterable([1, 2, 3]) for item in items: print(item)

This example delegates iteration to the built-in list iterator. The iter method returns an iterator for the internal data list.

The iter() function calls iter under the hood. This is the standard way to make an object work with for loops.

Custom Iterator Class

We can create a custom iterator by implementing both iter and next methods. This gives full control over iteration behavior.

custom_iterator.py

class CountDown: def init(self, start): self.current = start self.start = start

def __iter__(self):
    return self

def __next__(self):
    if self.current < 0:
        raise StopIteration
    else:
        result = self.current
        self.current -= 1
        return result

for num in CountDown(5): print(num)

This countdown iterator returns numbers from start down to 0. The iter returns self since the class is its own iterator.

The next method controls the iteration logic and raises StopIteration when done. This is required by the iterator protocol.

Generator Function as iter

Instead of a separate iterator class, we can use a generator function in iter. This is often more concise and readable.

generator_iter.py

class Squares: def init(self, limit): self.limit = limit

def __iter__(self):
    for i in range(1, self.limit + 1):
        yield i * i

for square in Squares(5): print(square)

This example generates squares of numbers up to a limit. The iter method uses yield to create a generator iterator automatically.

Generator functions simplify iterator implementation by handling the next and state management automatically. They’re memory efficient too.

Iterating Over Custom Data Structure

iter is particularly useful for custom data structures where you want to control how elements are accessed during iteration.

custom_structure.py

class TreeNode: def init(self, value, left=None, right=None): self.value = value self.left = left self.right = right

def __iter__(self):
    if self.left:
        yield from self.left
    yield self.value
    if self.right:
        yield from self.right

tree = TreeNode(4, TreeNode(2, TreeNode(1), TreeNode(3)), TreeNode(6, TreeNode(5), TreeNode(7)) )

for value in tree: print(value) # Prints 1-7 in order

This binary tree implements in-order traversal through iter. The yield from delegates to child nodes’ iterators recursively.

This pattern works for any tree-like structure where you want to hide the complexity of traversal behind a simple iteration interface.

Lazy Evaluation with iter

iter can implement lazy evaluation, where values are computed only when needed during iteration, saving memory and CPU cycles.

lazy_eval.py

class Fibonacci: def iter(self): a, b = 0, 1 while True: yield a a, b = b, a + b

import itertools for fib in itertools.islice(Fibonacci(), 10): print(fib)

This Fibonacci sequence generator computes numbers on demand. The infinite sequence is safely used with itertools.islice for demonstration.

Lazy evaluation is powerful for large or infinite sequences where precomputing all values would be impractical or impossible.

Best Practices

  • Return a new iterator: Each iter call should return a fresh iterator

  • Implement both protocols: For custom iterators, implement both iter and next

  • Consider generators: Generator functions often simplify iterator implementation

  • Handle state properly: Ensure iterator state is reset for each new iteration

  • Use StopIteration: Signal iteration completion properly with StopIteration

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.

ad ad