Python Iterators

Learn about iterators in Python and how to create your own iterable objects.

Python Iterators

An iterator is an object that contains a countable number of values.

An iterator is an object that can be iterated upon, meaning that you can traverse through all the values.

Technically, in Python, an iterator is an object which implements the iterator protocol, which consist of the methods __iter__() and __next__().

Iterator vs Iterable

Lists, tuples, dictionaries, and sets are all iterable objects. They are iterable containers which you can get an iterator from.

All these objects have a iter() method which is used to get an iterator:

Example - Return an iterator from a tuple, and print each value:

mytuple = ("apple", "banana", "cherry")
myit = iter(mytuple)

print(next(myit))
print(next(myit))
print(next(myit))

Even strings are iterable objects, and can return an iterator:

Example - Strings are also iterable objects, containing a sequence of characters:

mystr = "banana"
myit = iter(mystr)

print(next(myit))
print(next(myit))
print(next(myit))
print(next(myit))
print(next(myit))
print(next(myit))

Looping Through an Iterator

We can also use a for loop to iterate through an iterable object:

Example - Iterate the values of a tuple:

mytuple = ("apple", "banana", "cherry")

for x in mytuple:
    print(x)

Example - Iterate the characters of a string:

mystr = "banana"

for x in mystr:
    print(x)

The for loop actually creates an iterator object and executes the next() method for each loop.

Create an Iterator

To create an object/class as an iterator you have to implement the methods __iter__() and __next__() to your object.

As you have learned in the Python Classes/Objects chapter, all classes have a function called __init__(), which allows you to do some initializing when the object is being created.

The __iter__() method acts similar, you can do operations (initializing etc.), but must always return the iterator object itself.

The __next__() method also allows you to do operations, and must return the next item in the sequence.

Example - Create an iterator that returns numbers, starting with 1, and each sequence will increase by one (returning 1,2,3,4,5 etc.):

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))

StopIteration

The example above would continue forever if you had enough next() statements, or if it was used in a for loop.

To prevent the iteration to go on forever, we can use the StopIteration statement.

In the __next__() method, we can add a terminating condition to raise an error if the iteration is done a specified number of times:

Example - Stop after 20 iterations:

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)

Practical Iterator Examples

Fibonacci Iterator

class Fibonacci:
    def __init__(self, max_count):
        self.max_count = max_count
        self.count = 0
        self.current = 0
        self.next_val = 1
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.count < self.max_count:
            result = self.current
            self.current, self.next_val = self.next_val, self.current + self.next_val
            self.count += 1
            return result
        else:
            raise StopIteration

# Usage
fib = Fibonacci(10)
for num in fib:
    print(num, end=" ")
print()  # 0 1 1 2 3 5 8 13 21 34

Even Numbers Iterator

class EvenNumbers:
    def __init__(self, start=0, end=100):
        self.start = start if start % 2 == 0 else start + 1
        self.end = end
        self.current = self.start
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current <= self.end:
            result = self.current
            self.current += 2
            return result
        else:
            raise StopIteration

# Usage
even_nums = EvenNumbers(10, 20)
for num in even_nums:
    print(num, end=" ")
print()  # 10 12 14 16 18 20

File Line Iterator

class FileLineIterator:
    def __init__(self, filename):
        self.filename = filename
        self.file = None
    
    def __iter__(self):
        self.file = open(self.filename, 'r')
        return self
    
    def __next__(self):
        if self.file:
            line = self.file.readline()
            if line:
                return line.strip()
            else:
                self.file.close()
                raise StopIteration
        else:
            raise StopIteration

# Usage (assuming you have a file called 'data.txt')
# file_iter = FileLineIterator('data.txt')
# for line in file_iter:
#     print(line)

Built-in Iterator Functions

enumerate()

Returns an iterator that produces tuples containing indices and values:

fruits = ['apple', 'banana', 'cherry']

# Using enumerate
for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

# Starting from a different number
for index, fruit in enumerate(fruits, start=1):
    print(f"{index}: {fruit}")

zip()

Combines multiple iterables into tuples:

names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]
cities = ['New York', 'London', 'Tokyo']

# Zip multiple lists
for name, age, city in zip(names, ages, cities):
    print(f"{name} is {age} years old and lives in {city}")

# Zip with different lengths (stops at shortest)
numbers = [1, 2, 3, 4, 5]
letters = ['a', 'b', 'c']
for num, letter in zip(numbers, letters):
    print(f"{num}: {letter}")  # Only prints 3 pairs

reversed()

Returns an iterator that accesses the sequence in reverse order:

numbers = [1, 2, 3, 4, 5]

# Reverse iteration
for num in reversed(numbers):
    print(num, end=" ")
print()  # 5 4 3 2 1

# Works with strings too
for char in reversed("hello"):
    print(char, end="")
print()  # olleh

Generator Functions

Generators are a simpler way to create iterators using the yield keyword:

Example - Simple generator function:

def count_up_to(max_count):
    count = 1
    while count <= max_count:
        yield count
        count += 1

# Usage
counter = count_up_to(5)
for num in counter:
    print(num, end=" ")
print()  # 1 2 3 4 5

Example - Fibonacci generator:

def fibonacci_generator(max_count):
    a, b = 0, 1
    count = 0
    while count < max_count:
        yield a
        a, b = b, a + b
        count += 1

# Usage
fib_gen = fibonacci_generator(10)
for num in fib_gen:
    print(num, end=" ")
print()  # 0 1 1 2 3 5 8 13 21 34

Example - Reading large files efficiently:

def read_large_file(filename):
    """Generator to read large files line by line without loading entire file into memory"""
    with open(filename, 'r') as file:
        for line in file:
            yield line.strip()

# Usage
# for line in read_large_file('large_file.txt'):
#     process_line(line)  # Process one line at a time

Generator Expressions

Generator expressions provide a concise way to create generators:

# Generator expression for squares
squares = (x**2 for x in range(10))
for square in squares:
    print(square, end=" ")
print()  # 0 1 4 9 16 25 36 49 64 81

# Generator expression with condition
even_squares = (x**2 for x in range(10) if x % 2 == 0)
for square in even_squares:
    print(square, end=" ")
print()  # 0 4 16 36 64

# Memory efficient processing
def process_numbers(numbers):
    return (x * 2 for x in numbers if x > 5)

large_numbers = range(1000000)
processed = process_numbers(large_numbers)
# Only processes numbers as needed, not all at once

Iterator Tools

The itertools module provides many useful iterator functions:

import itertools

# count() - infinite counting
counter = itertools.count(10, 2)  # Start at 10, step by 2
for i, num in enumerate(counter):
    if i >= 5:
        break
    print(num, end=" ")
print()  # 10 12 14 16 18

# cycle() - infinite cycling through sequence
colors = itertools.cycle(['red', 'green', 'blue'])
for i, color in enumerate(colors):
    if i >= 7:
        break
    print(color, end=" ")
print()  # red green blue red green blue red

# repeat() - repeat a value
repeated = itertools.repeat('hello', 3)
for item in repeated:
    print(item, end=" ")
print()  # hello hello hello

# chain() - chain multiple iterables
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
chained = itertools.chain(list1, list2, list3)
for item in chained:
    print(item, end=" ")
print()  # 1 2 3 4 5 6 7 8 9

# combinations() - generate combinations
from itertools import combinations
items = ['A', 'B', 'C', 'D']
for combo in combinations(items, 2):
    print(combo, end=" ")
print()  # ('A', 'B') ('A', 'C') ('A', 'D') ('B', 'C') ('B', 'D') ('C', 'D')

Performance Benefits

Iterators and generators provide memory efficiency and lazy evaluation:

Example - Memory comparison:

import sys

# List comprehension - creates entire list in memory
list_comp = [x**2 for x in range(1000000)]
print(f"List size: {sys.getsizeof(list_comp)} bytes")

# Generator expression - creates iterator, not full list
gen_exp = (x**2 for x in range(1000000))
print(f"Generator size: {sys.getsizeof(gen_exp)} bytes")

# The generator uses much less memory!

# Processing large datasets efficiently
def process_large_dataset():
    # Instead of loading all data into memory
    # data = load_all_data()  # Could use lots of memory
    # for item in data:
    #     process(item)
    
    # Use generator to process one item at a time
    for item in data_generator():
        yield process_item(item)

def data_generator():
    # Simulate reading data one piece at a time
    for i in range(1000000):
        yield f"data_item_{i}"

def process_item(item):
    return item.upper()

# Usage
processed_data = process_large_dataset()
# Only processes items as needed, not all at once

Iterable vs Iterator

An iterable is anything you can loop over (list, string, dict). An iterator is the object that produces items one at a time via __next__(). iter() turns an iterable into an iterator.

nums = [10, 20, 30]
it = iter(nums)          # get an iterator
print(next(it))          # 10
print(next(it))          # 20
print(next(it))          # 30
# next(it)               # StopIteration -> loop would end here

Generators: Lazy Iterators

A generator function uses yield to produce values lazily — one at a time, without building the whole sequence in memory. Perfect for large or infinite streams.

def countdown(n):
    while n > 0:
        yield n           # pause and hand back a value
        n -= 1

for x in countdown(3):
    print(x)              # 3 2 1

# generator expression - like a comprehension with ()
squares = (x * x for x in range(1_000_000))   # uses almost no memory
print(next(squares))     # 0

Use a generator when data is large or streamed — it computes values on demand instead of storing them all.

Try It Yourself

Exercise 1: Write a generator evens(limit) that yields even numbers up to limit.

Show solution
def evens(limit):
    for n in range(0, limit + 1, 2):
        yield n

print(list(evens(10)))   # [0, 2, 4, 6, 8, 10]

Exercise 2: Use next() to pull the first two items from iter("hi").

Show solution
it = iter("hi")
print(next(it), next(it))   # h i

Key Takeaways

  • Iterables produce iterators via iter(); iterators yield items via next().
  • Generators (yield) produce values lazily and use little memory.
  • StopIteration ends the sequence (handled automatically by for).

📘 Real-World Deep Dive

Iterators are the protocol behind every <code>for</code> loop and every generator expression. <code>__iter__</code>/<code>__next__</code> is the whole API; <code>itertools</code> and generators give you a 100-line library of operations for free.

Real-Life Scenario

A chunked reader over a large streaming source — produces fixed-size lists, never materialises the whole stream, and can be combined with the rest of the standard library.

Real-Life Example

from typing import Iterator, Iterable, TypeVar

T = TypeVar("T")

def chunked(seq: Iterable[T], size: int) -> Iterator[list[T]]:
    """Yield successive size-sized chunks from any iterable."""
    chunk: list[T] = []
    for item in seq:
        chunk.append(item)
        if len(chunk) >= size:
            yield chunk
            chunk = []
    if chunk:
        yield chunk  # trailing partial

def take(seq: Iterable[T], n: int) -> Iterator[T]:
    """Yield the first n elements without materialising the rest."""
    it = iter(seq)
    for _ in range(n):
        try:
            yield next(it)
        except StopIteration:
            return

total = sum(int(x) for x in take(chunked(range(1_000_000), 100_000), 7))
print("sum of first 700k ints:", total)
print("chunks of 3:", list(chunked(range(7), 3)))

Expected Output

sum of first 700k ints: 245003500
chunks of 3: [[0, 1, 2], [3, 4, 5], [6]]

Common mistakes

  • Iterators are single-use — once exhausted (StopIteration), you must rebuild them or call iter(...) again.
  • A generator that yields inside a loop body silently captures variables; be careful with closures.
  • Generator-protocol classes (__iter__/__next__) need a __iter__ returning self.

🚀 Performance & Best Practices

  • Generator expressions beat list comprehensions when you only need a one-pass iteration — they hold zero items in memory.
  • itertools.islice is C-fast for "take N elements"; use it instead of hand-rolled slicing.
  • For huge datasets, write streaming readers that yield rows.

🧪 Try It Yourself

  1. Refactor chunked into a class that supports len() and __getitem__.
  2. Add a sliding_window(seq, width) generator that yields lists of width consecutive items.
  3. Time chunked(range(10_000_000), 1000) as a generator vs. a list.

FAQ: Python Iterators

Common questions about this page.

What is Python Iterators?

Python Iterators is a Python Tutorial lesson that explains python iterators in Python. Learn about iterators in Python and how to create your own iterable objects. Copy the samples and run them in the Python editor. It is written for beginners who want a clear definition and working examples.

Should I run python iterators examples locally for better learning?

Yes. Use the browser editor on StudyGrid for a quick check, then Download the example and run it on your computer. Local runs show real errors and the real toolchain, which is one of the fastest ways to learn python iterators in this Python Tutorial Python lesson (Python Iterators).

How do I use python iterators in Python?

To use python iterators in Python, follow the examples on this StudyGrid page. Copy a snippet, run it in the browser, then Download and run it locally for better learning. Change the values and compare the output.

What is the syntax of python iterators?

This Python Iterators tutorial shows python iterators syntax with short Python examples. Use the code blocks in this lesson for the exact statements, then try them in your editor.

Python Iterators example for beginners

Yes. This page includes a beginner python iterators example you can copy and run. It is designed for searches such as "python iterators for beginners", "python iterators example", and "how to use python iterators".

What are common mistakes with python iterators?

Common python iterators mistakes include wrong syntax, mixing types, and skipping practice. Work through this Python Tutorial chapter in order, run every example, and check the output before moving on.

Why should I learn python iterators?

Python Iterators is used in real Python work. Learning python iterators helps you write clearer programs and continue the Python Tutorial tutorial on StudyGrid.

Is Python Iterators free to learn online?

Yes. You can learn python iterators free on StudyGrid (studygrid.in). This chapter is part of the Python Tutorial path and includes examples, syntax, and next-step links.