Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Генераторы в Python — назначение и возможности
Генератор — это функция, которая вместо возвращения всех значений сразу (return), выдаёт их по одному через yield. Это мощный инструмент, который позволяет писать более эффективный и читаемый код.
Основная идея
# Обычная функция — возвращает список
def get_numbers_list():
result = []
for i in range(5):
result.append(i)
return result # Всё сразу в памяти
# Генератор — выдаёт по одному
def get_numbers_generator():
for i in range(5):
yield i # Выдаёт одно значение, приостанавливает выполнение
print(get_numbers_list()) # [0, 1, 2, 3, 4]
print(get_numbers_generator()) # <generator object>
for num in get_numbers_generator():
print(num) # 0, 1, 2, 3, 4
Что позволяют делать генераторы
1. Экономия памяти
Генератор не хранит все значения в памяти одновременно, а вычисляет их по мере необходимости.
# Обычный подход — много памяти
def squares_list(n):
return [i**2 for i in range(n)]
result = squares_list(1000000) # Создаёт список из 1 млн элементов
print(result[0]) # 0
# Генератор — минимум памяти
def squares_gen(n):
for i in range(n):
yield i**2
gen = squares_gen(1000000) # Объект генератора, практически без памяти
print(next(gen)) # 0 — вычислилось одно значение
2. Ленивое вычисление (Lazy evaluation)
Значения вычисляются только когда они нужны, а не заранее.
def process_large_file(filename):
"""Читает файл по строкам, не загружая весь файл в памяти"""
with open(filename) as f:
for line in f: # f сам по себе генератор
yield line.strip()
for line in process_large_file("huge_file.txt"):
if "error" in line:
print(line) # Обрабатываем строку
# Файл читается по мере необходимости
3. Упрощение асинхронного кода
Генераторы с yield позволяют писать асинхронный код синтаксически просто.
import asyncio
async def fetch_data():
data = []
for i in range(5):
await asyncio.sleep(0.1)
data.append(f"item_{i}")
return data
# Со старым синтаксисом генераторов-корутин
def old_coroutine():
result = yield from fetch_data() # Было до async/await
return result
4. Создание бесконечных последовательностей
Можно создавать потоки данных, которые работают бесконечно.
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
# В памяти хранится только текущее число, а не весь счётчик
5. Трубопровод обработки данных (Pipeline)
Можно строить цепочки генераторов, где каждый преобразует данные.
def read_data(filename):
"""Читает строки из файла"""
with open(filename) as f:
for line in f:
yield line.strip()
def filter_comments(lines):
"""Фильтрует строки без комментариев"""
for line in lines:
if not line.startswith("#"):
yield line
def parse_numbers(lines):
"""Парсит числа из строк"""
for line in lines:
try:
yield int(line)
except ValueError:
pass
# Цепочка обработки
pipeline = parse_numbers(filter_comments(read_data("data.txt")))
for number in pipeline:
print(f"Processed: {number}")
# Каждая функция обрабатывает по одному элементу,
# минимум памяти, максимум эффективность
6. Состояние между вызовами
Генератор сохраняет своё состояние между вызовами yield.
def fibonacci():
"""Генератор последовательности Фибоначчи"""
a, b = 0, 1
while True:
yield a
a, b = b, a + b # Состояние сохраняется
fib = fibonacci()
for _ in range(7):
print(next(fib)) # 0, 1, 1, 2, 3, 5, 8
7. Двусторонняя коммуникация с send()
Можно отправлять значения в генератор через send().
def echo():
"""Генератор, который получает и отправляет данные"""
while True:
value = yield
print(f"Получено: {value}")
gen = echo()
next(gen) # Запускаем генератор до первого yield
gen.send("Hello") # Отправляем значение
gen.send("World") # Отправляем ещё
8. Альтернатива циклам с состоянием
def iterate_with_progress():
"""Альтернатива сложному циклу с состоянием"""
total = 100
for i in range(total):
progress = (i + 1) / total * 100
yield {"current": i + 1, "progress": progress}
for item in iterate_with_progress():
print(f"{item['current']}/100 ({item['progress']:.1f}%)")
Практический пример: обработка больших данных
def read_csv_generator(filename, chunk_size=100):
"""Читает CSV файл батчами, без загрузки в памяти"""
batch = []
with open(filename) as f:
for line in f:
batch.append(line.strip().split(","))
if len(batch) == chunk_size:
yield batch
batch = []
if batch: # Последний батч
yield batch
def process_data(filename):
"""Обработка больших CSV файлов"""
for batch in read_csv_generator(filename, chunk_size=1000):
# Обрабатываем батч из 1000 строк
processed = [row for row in batch if row[0] != ""]
print(f"Processed {len(processed)} rows")
process_data("huge_data.csv")
# Файл читается и обрабатывается батчами
# Памяти используется максимум для одного батча (1000 строк)
Сравнение: список vs генератор
# Список — всё в памяти
data_list = [i**2 for i in range(1000000)] # ~50 МБ памяти
first = data_list[0]
# Генератор — ленивое вычисление
data_gen = (i**2 for i in range(1000000)) # ~1 КБ памяти
first = next(data_gen)
Встроенные генераторы в Python
range(100) # Генератор чисел
open("file.txt") # Генератор строк файла
enumerate([1, 2, 3]) # Генератор пар (индекс, значение)
zip([1, 2], ["a", "b"]) # Генератор кортежей
map(str, [1, 2, 3]) # Генератор преобразованных значений
filter(lambda x: x > 2, [1, 2, 3]) # Генератор отфильтрованных значений
Итог
Генераторы позволяют:
- Экономить память при работе с большими данными
- Ленивое вычисление — вычислять только при необходимости
- Писать элегантный код с цепочками преобразований
- Работать с бесконечными потоками данных
- Упрощать асинхронный код через корутины
Это один из самых мощных и элегантных инструментов Python для работы с данными.