Типовая ситуация: вы пишете скрипт для обработки лог-файла на 5 ГБ, запускаете — и через несколько секунд получаете MemoryError. Приложение съедает всю оперативную память, падает, а задача остаётся нерешённой.
По данным Python Developers Survey 2024 (Python Software Foundation и JetBrains, более 30 000 разработчиков из почти 200 стран), 48% Python-разработчиков используют язык для анализа данных, а 41% — для машинного обучения.
Данная статья пригодится тем, кто работает с большими объёмами данных — парсит сайты, обрабатывает API, анализирует логи или стримит информацию из внешних источников. Разберём, что такое генераторы в Python, как они решают проблему памяти и когда их применять.
Что такое генераторы в Python и чем они отличаются от обычных функций
Представьте: вам нужно обработать миллион записей из базы. Стандартный подход — загрузить всё в список и начать обработку. Результат: приложение тормозит или вылетает. Генераторы решают эту проблему по-другому.
Генератор в Python является объектом, создающим элементы последовательности по требованию. Классическая функция собирает полный результат до возврата, занимая память под каждый элемент. Генератор производит один элемент, отдаёт вызывающему коду, замирает — и ждёт запроса следующего.
Генератор реализует протокол итератора (iterator) с точки зрения языка. Любой iterable-объект (список, словарь) держит данные целиком. Генератор хранит лишь текущее состояние и инструкцию для создания следующего значения.

Сравним два подхода:
#Классическая функция
def create_sequence():
items = []
for num in range(1000000):
items.append(num * 2)
return items # Держит всё в оперативной памяти
#Функция-генератор
def generate_sequence():
for num in range(1000000):
yield num * 2 # Создаёт элемент при запросе, память ~120 байт
Объект-генератор весит около 120 байт независимо от количества элементов. Миллион чисел в списке — примерно 8 000 000 байт.
Вызов create_sequence() формирует массив из миллиона элементов и возвращает его целиком. Функция generate_sequence() возвращает объект-генератор: каждый элемент вычисляется только тогда, когда его запрашивают.
Оператор yield в Python: что это, как он работает, что делает
Yield считается главным механизмом генераторов. Сранивая с return, который возвращает результат и завершает функцию, yield отдаёт значение, фиксирует текущую позицию в коде и все локальные переменные — затем останавливается. При повторном обращении выполнение продолжается со следующей строки после yield.
К генератору обращаются функцией next() или циклом for. Когда элементы закончились, Python генерирует исключение StopIteration. Цикл for обрабатывает его автоматически — вы этого не увидите.

Смотрим на примере:
def step_counter():
step = 0
while step < 3: # Ленивые вычисления
step += 1
yield step
counter = step_counter()
print(next(counter)) # 1
print(next(counter)) # 2
print(next(counter)) # 3
print(next(counter)) # StopIteration
Оператор yield фиксирует состояние всех переменных. Переменная step в примере не обнуляется — функция запоминает её значение и продолжает с того же места.
Этот принцип называют ленивыми вычислениями (lazy evaluation): элементы создаются не заранее, а по мере необходимости. Подход снижает нагрузку на память и повышает производительность — особенно когда вы прерываете обработку досрочно.
Генераторные выражения: компактная запись
Для простых случаев в Python есть краткая форма — генераторное выражение. Синтаксис напоминает list comprehension, но скобки круглые, а не квадратные.
Посмотрим на разницу:
#List comprehension — строит список в памяти
numbers_list = [x * 3 for x in range(1000000)] # ~8 МБ
#Генераторное выражение Python — вычисляет на лету
numbers_gen = (x * 3 for x in range(1000000)) # ~120 байт
() создаёт генератор, [] создаёт список. Генератор проходят один раз, список — сколько угодно.
Генераторные выражения удобны для однострочной логики с большим объёмом данных. Если логика сложная или нужен повторный проход — берите список либо пишите функцию с yield.
Добавляем условие:
positive_only = (x for x in range(-100, 100) if x > 0)
Этот генератор вернёт только положительные числа из диапазона. Вы экономите память — отрицательные значения вообще не создаются.
Когда генераторы экономят память и ускоряют код
Генераторы прежде всего полезны в трёх сценариях: работа с большими файлами, обработка бесконечных потоков данных (например, логи в реальном времени) и пагинация API.
Ниже — сравнение памяти: список vs генератор.

Производительность: ленивые вычисления позволяют досрочно выйти из цикла. Ищете первое совпадение в миллионе записей? Генератор остановится сразу после находки. Список обработает все элементы.
Используйте генераторы для файлов больше 100 МБ, потоковых данных и когда не нужен повторный обход.
Когда НЕ нужны генераторы: маленькие данные (до 10 000 элементов), когда требуется повторный обход (итератор одноразовый), или нужны индексы и срезы (list[5:10]). В этих случаях список удобнее и быстрее.
Генераторы упрощают код: не нужно вручную создавать промежуточные списки, следить за индексами и управлять памятью. Python делает это автоматически.
Практические примеры генераторов на Python: от простых до реальных задач

Покажем, как генераторы Python применяются на практике. Три примера: обработка логов, загрузка с API, бесконечные последовательности.
Построчное чтение лог-файла
def process_log(filepath):
with open(filepath, encoding=‘utf-8’) as file:
for line in file: # file — это iterable
yield line.rstrip() # Убираем перевод строки
for log_entry in process_log(‘server.log’):
if ‘CRITICAL’ in log_entry:
handle_error(log_entry) # Отрабатываем критические события
Файл читается построчно. Если логи весят 5 ГБ, генератор загружает в память только текущую строку. Попытка загрузить через file.readlines() закончится ошибкой MemoryError.
Загрузка данных с API через пагинацию
import requests
def load_api_pages(endpoint):
offset = 0
limit = 50
while True:
resp = requests.get(f’{endpoint}?offset={offset}&limit={limit}’)
items = resp.json().get(‘results’, [])
if not items:
break
yield items # Возвращаем порцию данных
offset += limit
for batch in load_api_pages(‘https://api.site.com/products’):
store_to_db(batch) # Сохраняем в базу сразу
Загружаете не 100 страниц разом, а запрашиваете следующую только после обработки предыдущей. Генератор экономит память и сетевой трафик. Нашли нужное на странице 7 — остальные 93 запроса не выполнятся.
Бесконечная последовательность Фибоначчи
def fib_sequence():
prev, curr = 0, 1
while True: # Бесконечно
yield prev
prev, curr = curr, prev + curr
fib = fib_sequence()
for _ in range(8):
print(next(fib)) # 0, 1, 1, 2, 3, 5, 8, 13
Этот генератор не завершается никогда — но вы берёте ровно столько элементов, сколько нужно. Попытка создать бесконечный список приведёт к переполнению памяти.
Эти примеры демонстрируют, как генераторы упрощают работу: читайте файлы любого размера, стримите API, генерируйте бесконечные последовательности — без нагрузки на память.
Типичные ошибки при работе с генераторами
Даже опытные разработчики иногда неправильно используют генераторы. Разберём четыре частые ошибки и способы их избежать.
Забывают, что генератор исчерпывается
seq = (x for x in range(5))
print(list(seq)) # [0, 1, 2, 3, 4]
print(list(seq)) # [] — уже пустой!
Генератор проходят только один раз. После обхода он исчерпан. Чтобы пройти заново, создайте новый объект-генератор или используйте список, если данных немного.
Вызывают list() на генераторе большого объёма
#Плохо: весь файл загрузится в память
all_lines = list(process_log(‘huge.log’)) # MemoryError
Функции list(), tuple(), sum() материализуют весь генератор сразу — все преимущества теряются. Обрабатывайте данные в цикле, либо используйте встроенные функции (max, min, any), которые работают с генераторами напрямую.
Забывают про StopIteration в ручном вызове next
gen = (x for x in range(2))
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # StopIteration — программа упадёт
Если вызываете next() вручную, ловите исключение или используйте значение по умолчанию: next(gen, None). В цикле for эта проблема не возникает — он обрабатывает StopIteration автоматически.
Используют генератор там, где нужны индексы
#Неудобно: генератор не поддерживает индексы
gen = (x**2 for x in range(100))
#gen[50] — ошибка! Нет метода getitem
Если нужен доступ по индексу или срез (data[10:20]), используйте список. Генераторы предназначены для последовательного обхода.
Используйте генератор только для однократного прохода. Нужен повторный обход, индексы или срезы — создайте список.
Как выбрать между генератором, списком и итератором
Три инструмента — генератор, список и итератор — решают схожие задачи, но для разных ситуаций. Вот быстрый алгоритм выбора.
Выбирайте генератор, когда:
- Данных больше 100 МБ или размер неизвестен заранее.
- Обрабатываете поток: логи, API, бесконечные последовательности.
- Нужен только один проход по данным.
- Важна производительность: ранний выход из цикла экономит ресурсы.
Выбирайте список, когда:
- Элементов меньше 10 000 и они помещаются в оперативную память.
- Нужен повторный обход: пройтись несколько раз.
- Требуются индексы (data[5]) или срезы (data[10:20]).
- Планируете изменять элементы на месте (data[3] = new_value).
Выбирайте итератор (кастомный класс), когда:
- Логика слишком сложная для генератора: нужны методы, атрибуты, управление состоянием.
- Требуется дополнительная функциональность (reset, skip, peek).
- Создаёте библиотеку: кастомный итератор даёт больше контроля.
Далее — практический пример: парсинг CSV:
#Файл 10 строк → список: удобно, быстро читается
data = list(csv.reader(open(‘small.csv’)))
#Файл 1 млн строк → генератор: экономия памяти
for row in csv.reader(open(‘large.csv’)):
process(row) # Обрабатываем построчно
#Нужен контроль: кастомный итератор
class CSVIterator:
def init(self, path):
self.file = open(path)
def reset(self):
self.file.seek(0)
#Начать сначала
Начните с генератора. Если понадобятся индексы или повторный обход — переключитесь на список. Кастомный итератор — только для сложной логики.
Этот алгоритм поможет быстро принять решение и избежать распространённых проблем: перерасхода памяти при использовании списков или неудобства при попытке повторно обойти генератор.
Заключение
Генераторы в Python — механизм для обработки больших объёмов данных без перегрузки оперативной памяти. Вместо создания полной коллекции генераторы применяют принцип ленивых вычислений: элементы производятся по мере запроса, а не все сразу.
Главное преимущество — контроль потребления памяти. Коллекция из миллиона целых чисел занимает около 8 МБ, объект-генератор — примерно 120 байт. Для файлов свыше 100 МБ, API с пагинацией и потоковой обработки это критическая разница.
Когда применять? Чтение логов, парсинг сайтов, стриминг данных из внешних источников. Когда не применять: маленькие данные (до 10 000 элементов), повторный обход, нужны индексы.
Следующий шаг: откройте свой проект, найдите список, который обрабатывает большие данные, и замените его на генератор. Проверьте потребление памяти до и после — разница вас удивит.
Вам нужна биржа фриланса для новичков или требуются разработчики сайтов?



Комментарии