Как не перегрузить память с помощью генераторов

Как не перегрузить память с помощью генераторов Генераторы списков и словарей

Привет! В Python списки удобны, но они хранят все элементы в памяти одновременно. Если данных мало — это не проблема. Но если у тебя миллионы строк или бесконечный поток данных — списки могут съесть всю память.

Здесь на помощь приходят генераторы.

В этой статье мы разберём:

  • Что такое генераторы и чем они отличаются от списков
  • Генераторные выражения
  • Ключевое слово yield
  • Как экономить память при работе с большими данными

Что нужно знать перед началом

Для этого урока тебе понадобится:

  • Установленный 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
  • Применили на примерах с файлами и бесконечными последовательностями
  • Получили практические рекомендации

КВИЗ

Что дальше?

Оцените статью
IMI-DS - PYTHON LERNEN
Содержание
Оглавление ×