Методический материал: Генераторы и итераторы в Python
Для кого этот материал
Материал рассчитан на новичков. Предполагается, что вы уже знаете:
- переменные
- циклы for
- функции
- списки (list)
Цель: понять, как Python перебирает данные, что такое итераторы, зачем нужны генераторы, и когда использовать каждый инструмент.
⸻
Глава 1. Что такое итерация
Начнём с простой мысли.
Когда вы пишете:
numbers = [10, 20, 30]
for number in numbers:
print(number)
Python перебирает элементы списка по одному:
- Берёт 10
- Берёт 20
- Берёт 30
- Останавливается
Этот процесс называется итерацией.
Итерация — это последовательный обход элементов объекта.
⸻
Что можно перебирать в Python
Объекты, которые можно перебирать циклом for, называются iterable (итерируемые объекты).
Примеры:
numbers = [1, 2, 3] # список
text = "hello" # строка
data = (10, 20, 30) # кортеж
my_set = {1, 2, 3} # множество
my_dict = {"a": 1} # словарь
Все они работают в for.
Пример:
for char in "Python":
print(char)
Результат:
P
y
t
h
o
n
⸻
Что реально делает цикл for
Многие думают, что for — магия.
На самом деле Python делает примерно следующее:
iterator = iter(numbers)
while True:
try:
value = next(iterator)
print(value)
except StopIteration:
break
Сейчас это может выглядеть страшно.
Разберём по частям.
⸻
Глава 2. Iterable и Iterator
Новички часто путают эти понятия.
Это ошибка.
Нужно чётко разделять:
Iterable
Объект, который можно начать перебирать.
Например:
numbers = [1, 2, 3]
Список — iterable.
Но сам список не хранит текущее положение перебора.
⸻
Iterator
Объект, который умеет выдавать следующий элемент.
У него есть состояние.
Он знает:
«Я уже отдал первые 2 элемента. Следующий — третий».
⸻
Получаем iterator из iterable
Для этого используется:
iter()
Пример:
numbers = [10, 20, 30]
iterator = iter(numbers)
print(iterator)
Результат:
<list_iterator object at ...>
Теперь у нас есть настоящий итератор.
⸻
next()
Чтобы получить следующий элемент, используется:
next()
Пример:
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
print(next(iterator))
Результат:
10
20
30
⸻
Что будет дальше?
Попробуем ещё раз:
print(next(iterator))
Ошибка:
StopIteration
Почему?
Потому что элементы закончились.
⸻
Важная идея
Итератор нельзя «отмотать назад».
Он одноразовый.
Пример:
numbers = [1, 2, 3]
iterator = iter(numbers)
for item in iterator:
print(item)
for item in iterator:
print(item)
Результат:
1
2
3
Второй цикл ничего не выведет.
Потому что итератор уже исчерпан.
⸻
Аналогия из жизни
Представьте рулон билетов.
Итерируемый объект (iterable) — это весь рулон.
Итератор (iterator) — человек, который отрывает билет за билетом.
Он помнит:
«Уже выдал 5 билетов».
Назад приклеить нельзя.
⸻
Глава 3. Создание собственного итератора
Теперь создадим свой итератор.
Допустим, хотим объект, который выдаёт числа от 1 до 5.
⸻
Метод iter
Python вызывает его, чтобы начать перебор.
⸻
Метод next
Python вызывает его, чтобы получить следующий элемент.
⸻
Пример:
class Counter:
def init(self, max_value):
self.current = 1
self.max_value = max_value
def iter(self):
return self
def next(self):
if self.current > self.max_value:
raise StopIteration
value = self.current
self.current += 1
return value
counter = Counter(5)
for number in counter:
print(number)
Результат:
1
2
3
4
5
⸻
Разбор по шагам
Шаг 1
Создаётся объект:
counter = Counter(5)
Состояние:
current = 1
max_value = 5
⸻
Шаг 2
Цикл вызывает:
iter(counter)
Срабатывает:
iter()
Возвращается:
self
⸻
Шаг 3
Цикл вызывает:
next(counter)
Срабатывает:
next()
Возвращается:
1
Затем:
current += 1
Теперь:
current = 2
И так далее.
⸻
Когда всё заканчивается
Python получает:
raise StopIteration
И завершает цикл.
⸻
Проблема итераторов
Посмотрите на этот код:
counter = Counter(1_000_000)
Если сделать всё через список:
numbers = list(range(1_000_000))
Python сразу создаст миллион чисел в памяти.
Это дорого.
Решение — генераторы.
⸻
Глава 4. Генераторы
Генератор — это упрощённый способ создания итераторов.
Вместо:
iter()
next()
StopIteration
Python делает всё за нас.
Главное слово:
yield
⸻
Первый генератор
def my_generator():
yield 1
yield 2
yield 3
gen = my_generator()
print(next(gen))
print(next(gen))
print(next(gen))
Результат:
1
2
3
⸻
Что делает yield
yield временно останавливает функцию, сохраняя её состояние.
Это ключевая идея.
Обычная функция:
def test():
return 10
После return функция закончилась.
⸻
С yield иначе:
def test():
yield 10
yield 20
Функция «замораживается».
Потом продолжает работу с того места, где остановилась.
⸻
Как это работает
Пример:
def generator():
print("start")
yield 1
print("middle")
yield 2
print("end")
yield 3
Запуск:
gen = generator()
print(next(gen))
print(next(gen))
print(next(gen))
Результат:
start
1
middle
2
end
3
Функция не выполняется целиком сразу.
Она запускается частями.
⸻
Почему генераторы полезны
Главное преимущество:
Экономия памяти
Плохо:
numbers = [x for x in range(1_000_000)]
Хорошо:
numbers = (x for x in range(1_000_000))
Во втором случае числа создаются по мере необходимости.
Это называется:
lazy evaluation — ленивые вычисления.
В следующей части: generator expressions, yield from, реальные примеры (чтение больших файлов, API, бесконечные последовательности), частые ошибки новичков и практические задачи.