Паттерн «Итератор» в Python
Представьте, что у вас есть банка, полная конфет разных цветов. Вы хотите попробовать эти конфеты одну за другой, но не хотите высыпать все конфеты сразу. В этот момент вы опускаете руку в банку и достаёте по одной конфете за раз — этот процесс и есть итерация.
В программировании,паттерн Итератор— это паттерн проектирования, который предоставляет способ последовательного доступа к элементам объекта-коллекции без раскрытия его внутреннего представления.
Основные концепции
Паттерн Итератор состоит из двух основных компонентов:
- Итератор: отвечает за определение интерфейса для доступа к элементам и их обхода
- Iterable: предоставляет метод для создания итераторов
В Python паттерн Итератор глубоко интегрирован в основные возможности языка, что делает наше программирование более элегантным и интуитивно понятным.
Зачем нам нужны итераторы?
Ограничения традиционного подхода
Давайте сначала рассмотрим пример, в котором не используются итераторы:
Пример
class BookCollection:
def __init__(self):
self.books = ["Python Introduction", "Introduction to Algorithms", "Design Patterns", "Data Structures"]
def get_books(self):
return self.books
# Использовать эту коллекцию
collection = BookCollection()
books = collection.get_books()
# Обход книг — раскрывает детали внутренней реализации
for i in range(len(books)):
print(books[i])
Анализ проблемы:
- Клиентский код должен знать внутреннюю структуру коллекции (список)
- Если внутренняя реализация коллекции изменится (например, со списка на словарь), весь клиентский код придётся изменить
- Логика обхода тесно связана с реализацией коллекции
Преимущества итераторов
Пример
for book in collection:
print(book)
Преимущества использования итераторов:
- Инкапсуляция: Скрывает внутреннюю реализацию коллекции
- Унифицированный интерфейс: Различные типы коллекций могут использовать один и тот же метод обхода
- Гибкость: Можно легко изменить реализацию коллекции
- Поддержка множественных обходов: Несколько операций обхода могут выполняться одновременно
Протокол итератора в Python
Python реализует протокол итератора с помощью двух специальных методов:
__iter__()Метод
- Возвращает объект итератора
- Итерируемый объект должен реализовать этот метод
__next__()Метод
- Возвращает следующий элемент последовательности
- Когда больше нет элементов, возбудить
StopIterationисключение
Давайте разберемся, как работают итераторы, с помощью блок-схемы:

Создание пользовательских итераторов
Способ 1: реализация итератора с помощью класса
Давайте создадим пользовательский итератор книг:
Пример
"""Класс итератора книг"""
def __init__(self, books):
self.books = books
self.index = 0
def __iter__(self):
"""Возвращает сам итератор"""
return self
def __next__(self):
"""Возвращает следующую книгу или возбуждает StopIteration, если книг больше нет"""
if self.index < len(self.books):
book = self.books[self.index]
self.index += 1
return book
else:
raise StopIteration
class BookCollection:
"""Класс итерируемой коллекции книг"""
def __init__(self):
self.books = ["Python Introduction", "Introduction to Algorithms", "Design Patterns", "Data Structures"]
def __iter__(self):
"""Возвращает экземпляр итератора"""
return BookIterator(self.books)
# Используйте пользовательский итератор
collection = BookCollection()
for book in collection:
print(f"Чтение: {book}")
Вывод:
正在阅读: Python入门 正在阅读: 算法导论 正在阅读: 设计模式 正在阅读: 数据结构
Способ 2: Использование функций-генераторов
Python предоставляет более краткий способ — функции-генераторы:
Пример
def __init__(self):
self.books = ["Python Introduction", "Introduction to Algorithms", "Design Patterns", "Data Structures"]
def __iter__(self):
"""Создание итератора с помощью функции-генератора"""
for book in self.books:
yield book
# Использование точно такое же
collection = BookCollection()
for book in collection:
print(f"Чтение: {book}")
Преимущества генераторов:
- Более чистый код
- Автоматически управляет сохранением состояния
- Лучшая производительность
Практические сценарии использования итераторов
Сценарий 1: Постраничное чтение данных
Пример
"""Имитация итератора данных с постраничной разбивкой"""
def __init__(self, total_items, page_size=3):
self.total_items = total_items
self.page_size = page_size
self.current_page = 0
def __iter__(self):
return self
def __next__(self):
start = self.current_page * self.page_size
end = start + self.page_size
if start >= self.total_items:
raise StopIteration
# Имитация чтения страницы данных из базы данных
page_data = list(range(start, min(end, self.total_items)))
self.current_page += 1
return page_data
# Использовать итератор с разбивкой на страницы
paginator = PaginatedData(10, 3) # Всего 10 записей, по 3 на страницу
for page_num, page_data in enumerate(paginator, 1):
print(f"Данные страницы {page_num}: {page_data}")
Вывод:
第1页数据: [0, 1, 2] 第2页数据: [3, 4, 5] 第3页数据: [6, 7, 8] 第4页数据: [9]
Сценарий 2: Генерация бесконечных последовательностей
Пример
"""Итератор последовательности Фибоначчи"""
def __init__(self, max_count=10):
self.max_count = max_count
self.count = 0
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
if self.count >= self.max_count:
raise StopIteration
result = self.a
self.a, self.b = self.b, self.a + self.b
self.count += 1
return result
# Сгенерировать последовательность Фибоначчи
fib = FibonacciIterator(8)
print("Последовательность Фибоначчи:", list(fib))
Вывод:
斐波那契数列: [0, 1, 1, 2, 3, 5, 8, 13]
Встроенные утилиты для итераторов
Python предоставляет богатый набор встроенных функций для работы с итераторами:
iter()иnext()Функция
Пример
# Использовать итератор вручную
iterator = iter(numbers)
print(next(iterator)) # Вывод: 1
print(next(iterator)) # Вывод: 2
print(next(iterator)) # Вывод: 3
enumerate()Функция
Пример
for index, fruit in enumerate(fruits):
print(f"Index {index}: {fruit}")
zip()Функция
Пример
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name} score: {score}")
Iterator против Iterable
Важно понимать разницу между этими двумя понятиями:
| Характеристика | Iterable | Iterator |
|---|---|---|
| Определение | Реализует__iter__()объект метода |
Реализует__iter__()и__next__()объект метода |
| Назначение | Можно итерировать | Фактически выполняет операцию итерации |
| State | Обычно не хранит состояние | Хранит состояние итерации (текущую позицию и т. д.) |
| Пример | Списки, кортежи, словари, строки | iter()Возвращаемый объект |
Диаграмма связей
Пример
A[Iterable] -->|вызывает iter| B[Iterator]
B -->|многократно вызывает next| C[Поэлементно]
C --> D[До StopIteration]
Лучшие практики и типичные ошибки
Лучшие практики
- Используйте генераторы для упрощения кода
Пример
def countdown(n):
while n > 0:
yield n
n -= 1
# Не рекомендуется: вручную реализовывать класс итератора
- Используйте встроенные функции
Пример
squares = (x*x for x in range(10)) # Генераторное выражение
# Не рекомендуется
class SquareIterator:
# ... многословная реализация
Типичные ошибки
Ошибка 1: путаница между итераторами и итерируемыми объектами
Пример
# Ошибка: сам список не является итератором
try:
next(numbers) # TypeError: объект 'list' не является итератором
except TypeError as e:
print(f"Ошибка: {e}")
# Правильно: сначала получите итератор
iterator = iter(numbers)
print(next(iterator)) # Вывод: 1
Ошибка 2: Продолжение использования исчерпанного итератора
Пример
iterator = iter(numbers)
print(list(iterator)) # Вывод: [1, 2, 3]
print(list(iterator)) # Вывод: [] - итератор исчерпан!
Практические упражнения
Упражнение 1: Создание пользовательского итератора
СоздатьCountdownкласс, реализующий итератор, который ведёт обратный отсчёт от указанного числа до 1:
Пример
def __init__(self, start):
self.start = start
def __iter__(self):
# Ваш код здесь
current = self.start
while current > 0:
yield current
current -= 1
# Протестируйте свою реализацию
for num in Countdown(5):
print(num) # Должно вывести: 5, 4, 3, 2, 1
Упражнение 2: Итератор строк файла
Создайте итератор, который читает файл построчно и добавляет номер строки перед каждой строкой:
Пример
def __init__(self, filename):
self.filename = filename
def __iter__(self):
with open(self.filename, 'r', encoding='utf-8') as file:
for line_num, line in enumerate(file, 1):
yield f"{line_num}: {line.rstrip()}"
Итог
Паттерн итератора — чрезвычайно важная концепция в программировании на Python; он позволяет нам:
- Единообразный доступ: обходить разные структуры данных одинаковым способом
- Инкапсуляция реализации: скрывать внутреннюю структуру коллекций
- Ленивые вычисления: генерировать данные только при необходимости, экономя память
- Композируемость: прекрасно работает с такими возможностями, как генераторы и включения
Запомните этот простой принцип:Любой объект, который реализует__iter__()метод, является итерируемым, а любой объект, который реализует__next__()метод, является итератором.