← Назад к вопросам
Какие преимущества у генераторов перед списками?
1.0 Junior🔥 131 комментариев
#Python
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Преимущества генераторов перед списками (Generators vs Lists)
1. Память (Memory Efficiency)
Список хранит ВСЕ элементы в памяти одновременно.
# Список: O(n) память
my_list = [i for i in range(1000000)] # 1 млн элементов
print(sys.getsizeof(my_list)) # ≈ 8 мегабайт
# Итератор через список: выделена вся память сразу
for item in my_list:
print(item)
Генератор создаёт элементы по требованию (lazy evaluation).
# Генератор: O(1) память
def my_generator():
for i in range(1000000):
yield i # Создаёт элемент ПО ТРЕБОВАНИЮ
# Память практически не растёт
gen = my_generator()
for item in gen:
print(item)
print(sys.getsizeof(gen)) # ≈ 100 байт (только объект генератора)
Сравнение:
import sys
# Список: 8 MB
my_list = list(range(1000000))
print(f"List size: {sys.getsizeof(my_list) / 1024 / 1024:.2f} MB")
# Генератор: 0.0001 MB
def my_gen():
for i in range(1000000):
yield i
gen = my_gen()
print(f"Generator size: {sys.getsizeof(gen) / 1024 / 1024:.4f} MB")
# ✓ Генератор экономит память в 80,000x раз!
2. Скорость (Performance)
Список: нужно время на создание
import time
# Список: 0.5 сек на создание
start = time.time()
my_list = [i for i in range(10000000)]
list_time = time.time() - start
print(f"List creation: {list_time:.3f}s") # ~0.5s
# Генератор: мгновенно
start = time.time()
def my_gen():
for i in range(10000000):
yield i
gen = my_gen()
gen_time = time.time() - start
print(f"Generator creation: {gen_time:.6f}s") # ~0.0001s
3. Ленивое вычисление (Lazy Evaluation)
Генератор вычисляет только то, что нужно ПРЯМО СЕЙЧАС.
# Список: вычислит ВСЕ, хотя может понадобиться только первые
my_list = [complex_calculation(i) for i in range(1000000)]
first_ten = my_list[:10] # Но мы используем только первые 10!
# Выбросили 9999990 вычислений в мусор
# Генератор: вычислит только первые 10
def my_gen():
for i in range(1000000):
yield complex_calculation(i)
gen = my_gen()
first_ten = list(itertools.islice(gen, 10)) # Вычислены только 10!
Практический пример: логирование файлов
# ❌ Плохо: списки
def read_large_log_file():
lines = [] # Загрузить весь файл в память
with open('huge.log', 'r') as f:
for line in f:
lines.append(line)
return lines # Может быть 10 GB в памяти!
# Использование
for line in read_large_log_file():
if 'ERROR' in line:
print(line)
break # Но прочитали весь файл, хотя нашли ошибку на 100-й строке!
# ✅ Хорошо: генератор
def read_large_log_file():
with open('huge.log', 'r') as f:
for line in f: # Строка за строкой
yield line
# Использование
for line in read_large_log_file():
if 'ERROR' in line:
print(line)
break # Прочитали только до первой ошибки, остальное не трогали
4. Обработка потоков данных (Streaming)
Генераторы идеальны для работы с потоками, которые можно комбинировать.
# Цепочка трансформаций данных
def read_csv_file(filename):
with open(filename, 'r') as f:
for line in f:
yield line.strip()
def parse_csv_line(lines):
headers = next(lines).split(',')
for line in lines:
values = line.split(',')
yield dict(zip(headers, values))
def filter_valid_users(records):
for record in records:
if record['age'].isdigit() and int(record['age']) >= 18:
yield record
def select_fields(records, fields):
for record in records:
yield {k: record[k] for k in fields if k in record}
# Комбинирование без промежуточных списков
pipeline = (
read_csv_file('users.csv') # ~100 MB
|> parse_csv_line # Парсинг
|> filter_valid_users # Фильтр
|> select_fields(records, ['name', 'email']) # Выбор полей
)
# Проитерировать: ОДНА строка за раз, без загрузки всего в память
for user in pipeline:
print(user)
# Общая память: ~1 KB (одна строка), а не ~100 MB!
5. Бесконечные последовательности
Список не может быть бесконечным, генератор — может.
# Бесконечный список: невозможно
# my_list = [1, 2, 3, 4, 5, ...] # MemoryError
# Бесконечный генератор: спокойно
def infinite_counter():
i = 0
while True:
yield i
i += 1
# Берём сколько нужно
for count in itertools.islice(infinite_counter(), 5):
print(count) # 0, 1, 2, 3, 4
# Генератор Фибоначчи
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib_gen = fibonacci()
first_10_fibs = list(itertools.islice(fib_gen, 10))
6. Data Engineering: обработка больших датасетов
Сценарий: обработка миллиона CSV файлов с миллионом строк каждый
# ❌ Плохо: список
def process_files_list(directory):
all_data = [] # ❌ В памяти 1 млн x 1 млн = 1 трлн записей???
for filename in os.listdir(directory):
df = pd.read_csv(filename)
all_data.append(df)
return pd.concat(all_data) # OOM (Out of Memory)
# ✅ Хорошо: генератор
def process_files_generator(directory):
for filename in os.listdir(directory):
df = pd.read_csv(filename)
for _, row in df.iterrows():
yield row # Одна строка за раз
# Обработка
for row in process_files_generator('/data'):
save_to_warehouse(row) # Загружаем построчно
# Память: всегда < 10 MB (одна строка)
# Альтернатива: дочанкирование
def process_files_chunked(directory, chunk_size=10000):
chunk = []
for filename in os.listdir(directory):
df = pd.read_csv(filename)
for _, row in df.iterrows():
chunk.append(row)
if len(chunk) >= chunk_size:
yield chunk
chunk = []
if chunk:
yield chunk
for chunk in process_files_chunked('/data'):
bulk_insert_to_warehouse(chunk) # Вставляем батчами
7. Generator expressions (компактная синтаксис)
# Список: скобки []
my_list = [i**2 for i in range(1000000)]
# Генератор: скобки ()
my_gen = (i**2 for i in range(1000000))
# Одно отличие, огромная разница в памяти
# Генератор в функциях
result = sum(i**2 for i in range(1000000)) # Хороший код
result = sum([i**2 for i in range(1000000)]) # Плохой код (промежуточный список)
8. Когда использовать список vs генератор
| Сценарий | Используй | Почему |
|---|---|---|
| Нужно mehrere times iterate | Список | Генератор одноразовый |
Нужен индекс: data[5] | Список | Генератор не поддерживает индексацию |
| Маленький набор (< 10k) | Список | Просто и быстро |
| Большой набор (> 1M) | Генератор | Память + скорость |
| Бесконечный поток | Генератор | Список не может быть бесконечным |
| Потоковая обработка | Генератор | Lazy evaluation |
Нужна длина len(data) | Список | Генератор не знает свою длину |
9. Контрольные минусы генераторов
# ❌ Генератор одноразовый
gen = (i for i in range(5))
list(gen) # [0, 1, 2, 3, 4]
list(gen) # [] (генератор исчерпан!)
# ❌ Нет индексации
gen = (i for i in range(5))
gen[0] # TypeError: 'generator' object is not subscriptable
# ❌ Нет len()
gen = (i for i in range(5))
len(gen) # TypeError: object of type 'generator' has no len()
# ✓ Если нужны эти свойства — используй список или itertools.tee()
from itertools import tee
gen1, gen2 = tee(my_generator()) # Два независимых генератора
10. Практический пример: ETL с генераторами
# Pipeline обработки 100 GB логов
def read_logs(file_path):
"""Читаем строку за строкой"""
with gzip.open(file_path, 'rt') as f:
for line in f:
yield line
def parse_logs(lines):
"""Парсим JSON логи"""
for line in lines:
try:
yield json.loads(line)
except json.JSONDecodeError:
continue # Пропускаем плохие строки
def filter_errors(records):
"""Оставляем только ERROR логи"""
for record in records:
if record.get('level') == 'ERROR':
yield record
def aggregate_by_hour(records, batch_size=10000):
"""Агрегируем по часам"""
batch = []
for record in records:
batch.append(record)
if len(batch) >= batch_size:
yield batch
batch = []
if batch:
yield batch
# Использование
pipeline = (
read_logs('/var/log/app.log.gz')
|> parse_logs
|> filter_errors
|> aggregate_by_hour
)
for batch in pipeline:
# Каждый батч: ~10k ERROR логов
save_to_warehouse(batch)
# Память: ~10 MB (только батч в памяти)
# Без генераторов пришлось бы загрузить 100 GB в память!
Заключение
Генераторы — это суперсила для Data Engineer'а:
✅ Преимущества:
- Минимальная память (O(1) vs O(n))
- Мгновенное создание
- Lazy evaluation (вычисляем только нужное)
- Ленивые цепочки трансформаций
- Бесконечные последовательности
❌ Недостатки:
- Одноразовый (не iterate дважды)
- Нет индексации и len()
- Медленнее, чем список в цикле (но экономит память)
Rule of thumb: Если работаешь с потоками > 1 млн элементов или с файлами > 100 MB — используй генератор. В 90% случаев Data Engineering это будет правильный выбор.