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

Что делать с большим списком, который используется только в цикле?

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")

Вывод

Большой список, используется только в цикле?

  1. Генератор/Iterator (default) — O(1) память, ленивое вычисление
  2. map/filter — функциональный подход
  3. Потоковая обработка — если данные из файла
  4. Список — только если нужен random access, len() или сортировка

Выбор генератора вместо списка часто даёт 2-3x улучшение в памяти и скорости для больших данных.

Что делать с большим списком, который используется только в цикле? | PrepBro