Привет! В Python списки удобны, но они хранят все элементы в памяти одновременно. Если данных мало — это не проблема. Но если у тебя миллионы строк или бесконечный поток данных — списки могут съесть всю память.
Здесь на помощь приходят генераторы.
В этой статье мы разберём:
- Что такое генераторы и чем они отличаются от списков
- Генераторные выражения
- Ключевое слово
yield - Как экономить память при работе с большими данными
- Что нужно знать перед началом
- Основная часть
- Списки vs генераторы
- Сравнение по памяти
- Генераторные выражения
- Ключевое слово yield
- Чтение больших файлов
- Бесконечные последовательности
- Комбинирование генераторов
- Примеры из реальной жизни
- Задачи для закрепления
- Нюансы и подводные камни
- Генератор можно обойти только один раз
- Генератор не поддерживает индексацию
- Генератор не показывает размер
- Частые ошибки и как их избежать
- Ошибка 1: Попытка повторно обойти генератор
- Ошибка 2: Использование генератора как списка
- Ошибка 3: Генератор с большими вычислениями
- Шпаргалка
- Заключение
- КВИЗ
- Что дальше?
Что нужно знать перед началом
Для этого урока тебе понадобится:
- Установленный Python
- Базовое понимание списков и циклов
Совет: Генераторы — это способ работать с данными, не загружая их все в память.
Основная часть
Списки vs генераторы
Список (хранит всё в памяти):
numbers = [x ** 2 for x in range(1000000)] # Создаёт список из 1 млн элементовГенератор (вычисляет по одному):
numbers = (x ** 2 for x in range(1000000)) # Создаёт генератор, элементы не хранятсяСовет: Генератор не создаёт список в памяти. Он выдаёт элементы по одному по запросу.
Сравнение по памяти
import sys
# Список
list_data = [x for x in range(1000000)]
print(f"Список: {sys.getsizeof(list_data)} байт")
# Генератор
gen_data = (x for x in range(1000000))
print(f"Генератор: {sys.getsizeof(gen_data)} байт")Результат:
Список: 8 000 000 байт
Генератор: 128 байтСовет: Разница огромная! Генератор занимает почти ничего, даже для миллиарда элементов.
Генераторные выражения
# Генераторное выражение (круглые скобки)
squares = (x ** 2 for x in range(10))
# Использование
for num in squares:
print(num)Важно: Генератор можно обойти только один раз.
squares = (x ** 2 for x in range(10))
print(list(squares)) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
print(list(squares)) # [] — пусто! Генератор уже исчерпан.Ключевое слово yield
def count_up_to(n):
i = 0
while i < n:
yield i
i += 1
for num in count_up_to(5):
print(num)Совет:
yield— это какreturn, но функция не завершается, а запоминает состояние и продолжает с того же места при следующем вызове.
Чтение больших файлов
def read_large_file(filename):
with open(filename, "r") as file:
for line in file:
yield line
# Использование
for line in read_large_file("big_file.txt"):
process(line) # Обрабатываем по одной строкеСовет: Так можно читать файлы любого размера — память не переполняется.
Бесконечные последовательности
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for i in range(10):
print(next(fib)) # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34Совет: Генераторы позволяют работать с бесконечными последовательностями, которые невозможно сохранить в памяти.
Комбинирование генераторов
def numbers():
for i in range(10):
yield i
def squares(nums):
for n in nums:
yield n ** 2
def evens(nums):
for n in nums:
if n % 2 == 0:
yield n
# Цепочка генераторов
result = evens(squares(numbers()))
for val in result:
print(val) # 0, 4, 16, 36, 64Примеры из реальной жизни
Пример 1. Обработка логов:
def parse_log(filename):
with open(filename, "r") as file:
for line in file:
if "ERROR" in line:
yield line.strip()
for error in parse_log("server.log"):
print(error)Пример 2. Бесконечный генератор ID:
def generate_ids():
i = 1
while True:
yield i
i += 1
ids = generate_ids()
print(next(ids)) # 1
print(next(ids)) # 2
print(next(ids)) # 3Задачи для закрепления
Задача 1. Напиши генератор, который выдаёт квадраты чисел от 0 до N.
Задача 2. Напиши генератор, который читает файл построчно и возвращает только строки, содержащие слово «ERROR».
Задача 3. Напиши бесконечный генератор, который выдаёт числа Фибоначчи.
Задача 4. Чем отличается генератор от списка?
Задача 5. Что произойдёт, если дважды обойти генератор?
Ответы:
Задача 1.
def squares(n):
for i in range(n + 1):
yield i ** 2Задача 2.
def error_lines(filename):
with open(filename, "r") as file:
for line in file:
if "ERROR" in line:
yield line.strip()Задача 3.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + bЗадача 4.
# Список хранит все элементы в памяти.
# Генератор вычисляет элементы по одному, экономя память.Задача 5.
# Генератор будет пустым при второй попытке обхода.Нюансы и подводные камни
Генератор можно обойти только один раз
Если нужно обойти данные несколько раз, используй список.
Генератор не поддерживает индексацию
Нельзя обратиться к элементу по индексу: gen[5] — ошибка.
Генератор не показывает размер
len(gen) — ошибка. Генератор не знает, сколько у него элементов.
Частые ошибки и как их избежать
Ошибка 1: Попытка повторно обойти генератор
Создавай новый генератор для каждого прохода.
Ошибка 2: Использование генератора как списка
Помни, что генератор не поддерживает индексацию и срезы.
Ошибка 3: Генератор с большими вычислениями
Если вычисления сложные, генератор может быть медленнее списка.
Шпаргалка
| Что нужно | Как пишется |
|---|---|
| Генераторное выражение | (x ** 2 for x in range(10)) |
| Функция-генератор | def gen(): yield value |
| Получить элемент | next(gen) |
| Обойти генератор | for x in gen: |
| Создать список из генератора | list(gen) |
Заключение
Сегодня мы:
- Узнали, как генераторы экономят память
- Разобрали генераторные выражения и
yield - Применили на примерах с файлами и бесконечными последовательностями
- Получили практические рекомендации








