История вопроса
Python начиная с версии 2.4 дополнил списковые выражения (list comprehensions) так называемыми "выражениями-генераторами". Они позволяют создавать ленивая последовательность значений, аналогичную генераторам, но в компактной и удобочитаемой форме.
Проблема
Списковые выражения ([x for x in iterable]) создают список, сразу загружая все элементы в память. Это неэффективно или даже опасно, если количество элементов очень большое. Генераторные функции (с использованием yield) более гибки, но требуют отдельного определения функции и большего числа строк кода.
Решение
Выражения-генераторы ((x for x in iterable)) предоставляют лаконичный синтаксис для произведения последовательностей ленивая (элементы вычисляются по мере необходимости, а не загружаются все сразу). Выглядят похоже на списковые выражения, но используют круглые скобки:
# Списковое выражение загружает все в память squares_list = [x**2 for x in range(10**6)] # Генератор-выражение: элементы поступают по мере запроса, память почти не используется squares_gen = (x**2 for x in range(10**6)) # Получить первые пять значений генератора for _ in range(5): print(next(squares_gen))
Ключевые особенности:
Можно ли "перепроходить" одно и то же выражение-генератор несколько раз?
Нет, после итерирования один раз генератор "исчерпывается". Для повторного обхода надо создать новый генератор или воспользоваться списковым выражением.
it = (x for x in range(3)) print(list(it)) # [0,1,2] print(list(it)) # [] — больше нельзя получить значения
Сохраняют ли генераторы состояние между использованиями?
Да, генераторное выражение сохраняет «позицию» между вызовами next() (или при очередной итерации), но не может быть сброшено к началу, если только вы не создадите новый объект.
Можно ли использовать генераторное выражение сразу несколько раз в одной строке?
Нет! Если вы "распаковываете" генератор сразу в несколько мест (например, в несколько функций одновременно, не возвращая его в список), часть данных теряется — каждое дочернее использование двигает указатель вперед.
g = (x for x in range(3)) print(sum(g), list(g)) # sum(g) получит все, list(g) останется пустым
В проекте для анализа больших файлов использовали:
data = (parse_line(line) for line in file) process(list(data)) other_process(list(data))
Плюсы:
Минусы:
Использовали list comprehension, если требуется повторное использование данных, или создают генератор для одноразового потребления:
# Генератор только для однократного анализа (например, подсчитать сумму) total = sum(parse_line(line) for line in file)
Плюсы:
Минусы: