← Назад к вопросам
Что делать с большим списком, который используется только в цикле?
1.7 Middle🔥 161 комментариев
#Git и VCS#Python Core#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Оптимизация больших списков в циклах
Это хороший вопрос о Performance Optimization. Ответ зависит от контекста, но есть несколько практических подходов.
Проблема: большой список, используется только в цикле
Сценарий:
# Плохо: создаём весь список в памяти, потом итерируем
data = list(range(1_000_000)) # Весь список в памяти
for item in data:
process(item)
Проблемы:
- Если список очень большой (миллионы элементов) — потребление памяти O(n)
- Если нужен только однократный проход — неэффективно
- GC (garbage collection) может быть slow для больших структур
Решение 1: Генератор вместо списка (ЛУЧШИЙ ВАРИАНТ)
# Хорошо: используем генератор — O(1) память
def generate_data():
for i in range(1_000_000):
yield i # Каждое значение на лету
# Итерируем
for item in generate_data():
process(item)
Преимущества:
- Память: O(1) вместо O(n)
- Ленивое вычисление (lazy evaluation)
- Если break внутри цикла — остальное не генерируется
Сравнение памяти:
import sys
# Список
my_list = list(range(1_000_000))
print(f"List size: {sys.getsizeof(my_list) / 1024 / 1024:.1f} MB") # ~40 MB
# Генератор
def my_gen():
for i in range(1_000_000):
yield i
my_generator = my_gen()
print(f"Generator size: {sys.getsizeof(my_generator) / 1024:.1f} KB") # ~0.2 KB
Решение 2: Iterator из существующих структур
Если уже есть список, но нужно пройтись один раз:
import itertools
data = [1, 2, 3, 4, 5]
# Можно просто итерировать (уже O(1) доступ)
for item in data:
process(item)
# Или если нужны окна (chunks)
for chunk in itertools.batched(data, size=100): # Python 3.12+
process_batch(chunk)
# Или infinite iterator
for item in itertools.cycle(data):
process(item)
Решение 3: map() для функционального стиля
# Вместо
results = [process(item) for item in range(1_000_000)]
# Используй
results = map(process, range(1_000_000))
# map() возвращает iterator, не список
for result in results:
handle(result)
# Или если всё-таки нужен список результатов
results = list(map(process, range(1_000_000)))
Решение 4: filter() для выборки
# Плохо: делаем весь список, потом фильтруем
data = list(range(1_000_000))
filtered = [x for x in data if x % 2 == 0] # Весь список в памяти
# Хорошо: фильтруем на лету
data_stream = filter(lambda x: x % 2 == 0, range(1_000_000))
for item in data_stream:
process(item)
Решение 5: Файловый ввод (для очень больших данных)
Если данные из файла (CSV, JSON Lines, etc):
# Плохо: загружаем весь файл в памяти
with open('huge_file.csv') as f:
data = [parse_line(line) for line in f] # Весь в памяти!
for item in data:
process(item)
# Хорошо: читаем построчно
def read_lines(filename):
with open(filename) as f:
for line in f:
yield parse_line(line)
for item in read_lines('huge_file.csv'):
process(item)
# Или с pandas chunk reading
import pandas as pd
for chunk in pd.read_csv('huge_file.csv', chunksize=10000):
process_dataframe(chunk)
Практический пример: оптимизация real-world кода
# БЫЛО (медленно и много памяти)
def process_users_old(user_ids: list[int]):
# Загружаем ВСЕХ пользователей из БД
users = db.session.query(User).filter(User.id.in_(user_ids)).all()
results = []
for user in users:
if user.is_active:
results.append(user.email)
return results
# СТАЛО (быстро и оптимально)
def process_users_new(user_ids: Iterator[int]):
# Используем iterator, не список
# Генерируем результаты на лету
for user_id in user_ids:
user = db.session.get(User, user_id) # Один запрос за раз
if user and user.is_active:
yield user.email # Генератор, не список
# Использование
for email in process_users_new(user_stream):
send_notification(email)
Когда всё-таки нужен СПИСОК (не генератор)
# Если нужна информация о размере
if len(data) > 100: # Не получится с генератором
process_in_batches(data)
# Если нужен random access
for i in random.sample(range(len(data)), k=10):
process(data[i]) # random access — нужен список
# Если нужна сортировка
for item in sorted(data, key=lambda x: x.priority):
process(item)
# Если нужны манипуляции (filter + map + sort одновременно)
result = list(filter(is_valid, map(transform, data)))
Правило большого пальца
# Если используется ОДИН раз в цикле без доступа по индексу
# → ГЕНЕРАТОР или ITERATOR
# Если нужна СЛУЧАЙНАЯ выборка, len(), индексный доступ
# → СПИСОК
# Если ОЧЕНЬ БОЛЬШИЕ данные (ГБ+) из файла
# → ПОТОКОВАЯ ОБРАБОТКА (generator)
Бенчмарк для демонстрации
import timeit
# Список
time_list = timeit.timeit(
'for x in range(1000000): pass',
number=10
)
# Генератор (функция, не вызывается)
def gen():
for x in range(1000000):
yield x
time_gen = timeit.timeit(
'for x in gen(): pass',
globals={'gen': gen},
number=10
)
print(f"List: {time_list:.3f}s")
print(f"Generator: {time_gen:.3f}s")
print(f"Generator быстрее в {time_list/time_gen:.2f}x")
Вывод
Большой список, используется только в цикле?
- Генератор/Iterator (default) — O(1) память, ленивое вычисление
- map/filter — функциональный подход
- Потоковая обработка — если данные из файла
- Список — только если нужен random access, len() или сортировка
Выбор генератора вместо списка часто даёт 2-3x улучшение в памяти и скорости для больших данных.