← Назад к вопросам

Как работают генераторы в Python?

1.3 Junior🔥 211 комментариев
#Python

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работают генераторы в Python

Генераторы — это мощная и элегантная функция Python, которые позволяют создавать итераторы более удобным способом. Они являются основой для работы с большими объёмами данных, так как позволяют работать с данными лениво (по мере необходимости), а не загружать всё в память сразу.

Что такое генератор?

Генератор — это функция, которая содержит одно или несколько выражений yield. Когда такая функция вызывается, она возвращает генератор-объект, а не результат функции.

def simple_generator():
    yield 1
    yield 2
    yield 3

gen = simple_generator()
print(gen)  # <generator object simple_generator at 0x...>
print(type(gen))  # <class 'generator'>

Как работает генератор?

Шаг за шагом:

  1. Функция-генератор не выполняется немедленно
  2. Вызов функции возвращает объект генератора
  3. При первом вызове next() функция выполняется до первого yield
  4. yield возвращает значение и приостанавливает функцию
  5. При следующем вызове next() функция продолжает выполнение после yield
  6. Когда функция завершается, выбрасывается StopIteration
def counter_gen():
    print("Starting generator")
    yield 1
    print("Continuing after 1")
    yield 2
    print("Continuing after 2")
    yield 3
    print("Done")

gen = counter_gen()
print(next(gen))  # Starting generator, returns 1
print(next(gen))  # Continuing after 1, returns 2
print(next(gen))  # Continuing after 2, returns 3
print(next(gen))  # Done, raises StopIteration

Использование в циклах

Часто генераторы используются в циклах for, которые автоматически вызывают next() и обрабатывают StopIteration:

def squares(n):
    for i in range(n):
        yield i * i

for square in squares(5):
    print(square)  # 0, 1, 4, 9, 16

Преимущества генераторов

1. Ленивое вычисление (Lazy Evaluation) Генератор вычисляет значения только когда они нужны:

# Плохо: создаёт список в памяти
def create_list(n):
    result = []
    for i in range(n):
        result.append(i * 2)
    return result

big_list = create_list(1000000)  # Загружается вся память

# Хорошо: генератор
def create_gen(n):
    for i in range(n):
        yield i * 2

gen = create_gen(1000000)  # Ничего не вычисляется!
for value in gen:
    print(value)  # Вычисляется по одному значению

2. Экономия памяти

# Для обработки 1 млн записей из БД
def fetch_records(db, query):
    cursor = db.execute(query)
    for row in cursor:
        yield row  # Одна запись в памяти, не всех млн

for record in fetch_records(db, "SELECT * FROM big_table"):
    process(record)

3. Чистый и читаемый код

def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

for num in fibonacci(10):
    print(num)

Практические примеры для Data Engineer

1. Чтение больших файлов

def read_large_csv(filepath, chunk_size=1000):
    with open(filepath) as f:
        chunk = []
        for line in f:
            chunk.append(line.strip().split(','))
            if len(chunk) == chunk_size:
                yield chunk
                chunk = []
        if chunk:
            yield chunk

for batch in read_large_csv('huge_file.csv'):
    process_batch(batch)  # Обрабатываем батч за батчем

2. Обработка потока данных

def process_stream(data_stream):
    for data in data_stream:
        if data['valid']:
            transformed = transform(data)
            yield transformed

for clean_data in process_stream(incoming_data):
    save_to_db(clean_data)

3. Бесконечные последовательности

def infinite_counter():
    i = 0
    while True:
        yield i
        i += 1

counter = infinite_counter()
for _ in range(5):
    print(next(counter))  # 0, 1, 2, 3, 4

Выражения-генераторы (Generator Expressions)

Аналогично list comprehension, но возвращает генератор:

# List comprehension (загружает в память)
squares_list = [x**2 for x in range(1000000)]

# Generator expression (лениво)
squares_gen = (x**2 for x in range(1000000))

for square in squares_gen:
    print(square)  # Вычисляется по одному

Send и Throw

Генераторы могут получать значения через send() и обрабатывать исключения через throw():

def echo_gen():
    while True:
        value = yield
        print(f"Received: {value}")

gen = echo_gen()
next(gen)  # Инициализируем
gen.send("Hello")  # Received: Hello
gen.close()  # Закрываем генератор

Производительность

import sys

# Сравнение памяти
list_of_ints = [i for i in range(100000)]
print(sys.getsizeof(list_of_ints))  # Много МБ

gen_of_ints = (i for i in range(100000))
print(sys.getsizeof(gen_of_ints))  # Несколько байт!

Best Practices для Data Engineer

1. Используй генераторы для больших данных

# Плохо: может не хватить памяти
all_records = load_all_from_db()
for record in all_records:
    process(record)

# Хорошо: потоковая обработка
for record in load_from_db_streaming():
    process(record)

2. Комбинируй генераторы

def read_files(patterns):
    for pattern in patterns:
        for filepath in glob.glob(pattern):
            yield filepath

def read_csvs(patterns):
    for filepath in read_files(patterns):
        for row in read_csv(filepath):
            yield row

for row in read_csvs(['data/*.csv', 'archive/*.csv']):
    process(row)

3. Обработка ошибок

def safe_generator(data_source):
    for item in data_source:
        try:
            yield process(item)
        except ProcessingError as e:
            logger.error(f"Error: {e}")
            continue

Выводы

Генераторы — это фундаментальный инструмент для работы с большими объёмами данных в Python. Они обеспечивают ленивое вычисление, экономят память и делают код чище. Для Data Engineer'ов это критически важно при работе с большими файлами, потоками данных и обработкой миллионов записей без перегрузки памяти.