Антон Ларичев
Генераторы и yield в Python — как создавать и использовать
Введение
Генераторы — один из самых мощных инструментов Python для работы с последовательностями данных. В отличие от обычных функций, которые возвращают результат целиком, генераторы выдают значения по одному, приостанавливая выполнение между вызовами. Это позволяет экономить память и обрабатывать потенциально бесконечные потоки данных.
В этой статье мы разберём, как работают генераторы, что делает ключевое слово yield, и в каких ситуациях генераторы незаменимы.
Что такое генератор
Генератор — это особый вид итератора, который создаётся с помощью функции, содержащей ключевое слово yield. Когда Python встречает yield в теле функции, он автоматически превращает эту функцию в генераторную.
def simple_generator():
yield 1
yield 2
yield 3
# Создаём объект генератора
gen = simple_generator()
# Получаем значения по одному
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
При вызове генераторной функции код внутри неё не выполняется сразу. Вместо этого Python возвращает объект генератора. Код выполняется только при запросе следующего значения через next() или цикл for.
Как работает yield
Ключевое слово yield работает как return, но с важным отличием: оно не завершает функцию, а приостанавливает её выполнение, сохраняя текущее состояние. При следующем вызове next() выполнение продолжается ровно с того места, где остановилось.
def countdown(n):
print("Начинаем обратный отсчёт")
while n > 0:
yield n
n -= 1
print("Готово!")
# Создаём генератор
counter = countdown(3)
print(next(counter))
# Выведет: Начинаем обратный отсчёт
# Выведет: 3
print(next(counter))
# Выведет: 2
print(next(counter))
# Выведет: 1
# При следующем вызове next() выведет "Готово!" и выбросит StopIteration
Обратите внимание: строка «Начинаем обратный отсчёт» выводится только при первом вызове next(), а не при создании генератора.
Генераторы и циклы for
Чаще всего генераторы используются в цикле for, который автоматически вызывает next() и обрабатывает исключение StopIteration:
def fibonacci(limit):
"""Генератор чисел Фибоначчи до заданного предела"""
a, b = 0, 1
while a < limit:
yield a
a, b = b, a + b
# Перебираем числа Фибоначчи до 100
for num in fibonacci(100):
print(num, end=" ")
# Выведет: 0 1 1 2 3 5 8 13 21 34 55 89
Экономия памяти
Главное преимущество генераторов — ленивые вычисления. Значения создаются по запросу, а не хранятся в памяти все сразу. Сравним список и генератор:
import sys
# Список — хранит все элементы в памяти
numbers_list = [x ** 2 for x in range(1000000)]
print(sys.getsizeof(numbers_list)) # ~8 МБ
# Генератор — хранит только текущее состояние
numbers_gen = (x ** 2 for x in range(1000000))
print(sys.getsizeof(numbers_gen)) # ~200 байт
Разница огромна: список занимает мегабайты, а генератор — всего несколько сотен байт, независимо от количества элементов.
Если вы хотите детальнее изучить Python и его продвинутые возможности — приходите на наш большой курс Python-разработчик с нуля. На курсе 180 уроков и 80 упражнений, AI-тренажёры для практики 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.
Генератор для чтения больших файлов
Один из классических сценариев применения генераторов — построчное чтение больших файлов:
def read_large_file(filepath):
"""Читает файл построчно, не загружая его целиком в память"""
with open(filepath, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()
# Обрабатываем файл любого размера
for line in read_large_file('huge_log.txt'):
if 'ERROR' in line:
print(line)
Этот подход позволяет обрабатывать файлы размером в десятки гигабайт без риска исчерпать оперативную память.
Цепочки генераторов
Генераторы можно объединять в цепочки, передавая результат одного генератора в другой. Это создаёт конвейер обработки данных:
def read_lines(filepath):
"""Читает строки из файла"""
with open(filepath, 'r') as f:
for line in f:
yield line.strip()
def filter_nonempty(lines):
"""Пропускает пустые строки"""
for line in lines:
if line:
yield line
def to_uppercase(lines):
"""Преобразует строки в верхний регистр"""
for line in lines:
yield line.upper()
# Конвейер обработки
pipeline = to_uppercase(filter_nonempty(read_lines('data.txt')))
for line in pipeline:
print(line)
Каждый генератор в цепочке обрабатывает по одному элементу за раз, что делает конвейер эффективным по памяти.
yield from — делегирование генераторов
Начиная с Python 3.3 можно использовать конструкцию yield from для делегирования работы другому генератору или итерируемому объекту:
def gen_numbers():
yield from range(3) # 0, 1, 2
yield from range(10, 13) # 10, 11, 12
for n in gen_numbers():
print(n, end=" ")
# Выведет: 0 1 2 10 11 12
yield from особенно полезен для рекурсивных генераторов:
def flatten(nested):
"""Рекурсивно разворачивает вложенные списки"""
for item in nested:
if isinstance(item, list):
yield from flatten(item)
else:
yield item
data = [1, [2, 3], [4, [5, 6]], 7]
print(list(flatten(data)))
# Выведет: [1, 2, 3, 4, 5, 6, 7]
Частые ошибки
- Повторное использование генератора. Генератор можно обойти только один раз. После исчерпания он не «перезаряжается» — нужно создавать новый объект.
- Вызов
next()без обработкиStopIteration. Когда генератор исчерпан,next()выбросит исключение. Используйтеnext(gen, default)для безопасного извлечения. - Попытка узнать длину генератора через
len(). Генераторы не поддерживаютlen(), так как количество элементов заранее неизвестно.
Частозадаваемые вопросы
Чем генератор отличается от обычного списка? Список хранит все элементы в памяти сразу, а генератор вычисляет их по одному, по мере необходимости. Генератор экономит память, но элементы можно обойти только один раз.
Когда стоит использовать генератор вместо списка? Используйте генераторы, когда работаете с большими объёмами данных, читаете файлы построчно, обрабатываете потоки или создаёте бесконечные последовательности. Если данных немного и нужен произвольный доступ по индексу — используйте список.
Можно ли преобразовать генератор в список?
Да, с помощью list(generator). Но это загрузит все элементы в память, что может нивелировать преимущество генератора.
Заключение
Генераторы и yield — фундаментальный инструмент Python для эффективной работы с данными. Они позволяют создавать ленивые последовательности, экономить память и строить элегантные конвейеры обработки. Понимание генераторов открывает путь к асинхронному программированию и продвинутым паттернам проектирования.
Для закрепления навыков работы с генераторами и другими продвинутыми возможностями Python рекомендуем курс Python-разработчик с нуля. В первых 3 модулях курса доступно бесплатное содержание, что позволяет освоить основы языка и понять структуру курса до покупки полного доступа.
Постройте личный план изучения Python до уровня Middle — бесплатно!
Python — часть карты развития Backend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Все гайды по Python
Лучшие курсы по теме

Основы Python
Антон Ларичев
Nest.js с нуля
Антон Ларичев