Выражения генератора и понимание списка - PullRequest
370 голосов
/ 07 сентября 2008

Когда вы должны использовать выражения генератора и когда вы должны использовать списочные выражения в Python?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

Ответы [ 9 ]

251 голосов
/ 07 сентября 2008

Хороший ответ Джона (что понимание списка лучше, если вы хотите повторять что-то несколько раз). Однако также стоит отметить, что вы должны использовать список, если вы хотите использовать любой из методов списка. Например, следующий код не будет работать:

def gen():
    return (something for something in get_some_stuff())

print gen()[:2]     # generators don't support indexing or slicing
print [5,6] + gen() # generators can't be added to lists

По сути, используйте выражение генератора, если все, что вы делаете, - это итерация один раз. Если вы хотите сохранить и использовать сгенерированные результаты, то вам, вероятно, лучше понять список.

Поскольку производительность является наиболее распространенной причиной выбора одного над другим, я советую не беспокоиться об этом и просто выбрать один; если вы обнаружите, что ваша программа работает слишком медленно, тогда и только тогда вам следует вернуться и заняться настройкой своего кода.

161 голосов
/ 07 сентября 2008

Итерации по генератору выражения или списка сделают то же самое. Тем не менее, список сначала создаст весь список в памяти, в то время как генератор выражение создаст элементы на лету, так что вы можете использовать его для очень больших (а также бесконечные!) последовательности.

90 голосов
/ 07 сентября 2008

Используйте списочные выражения, когда результат должен повторяться несколько раз или когда скорость имеет первостепенное значение. Используйте выражения генератора, где диапазон большой или бесконечный.

52 голосов
/ 04 апреля 2014

Важным моментом является то, что понимание списка создает новый список. Генератор создает итеративный объект, который будет «фильтровать» исходный материал на лету, когда вы будете использовать биты.

Представьте, что у вас есть файл журнала объемом 2 ТБ, называемый "принц", и вам нужно содержимое и длина для всех строк, начинающихся со слова "ВВОД".

Итак, попробуйте начать с составления списка:

logfile = open("hugefile.txt","r")
entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]

Это откладывает весь файл, обрабатывает каждую строку и сохраняет соответствующие строки в вашем массиве. Следовательно, этот массив может содержать до 2 ТБ контента. Это много оперативной памяти, и, вероятно, не практично для ваших целей.

Таким образом, вместо этого мы можем использовать генератор, чтобы применить «фильтр» к нашему контенту. На самом деле данные не читаются, пока мы не начнем итерацию по результату.

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))

Из нашего файла еще не было прочитано ни одной строки. Фактически, скажем, мы хотим отфильтровать наш результат еще больше:

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)

Все еще ничего не прочитано, но мы указали два генератора, которые будут работать с нашими данными так, как мы хотим.

Запишем наши отфильтрованные строки в другой файл:

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)

Теперь читаем входной файл. Поскольку наш цикл for продолжает запрашивать дополнительные строки, генератор long_entries запрашивает строки из генератора entry_lines, возвращая только те, длина которых превышает 80 символов. И, в свою очередь, генератор entry_lines запрашивает строки (отфильтрованные как указано) от итератора logfile, который, в свою очередь, читает файл.

Таким образом, вместо того, чтобы «выталкивать» данные в вашу функцию вывода в виде полностью заполненного списка, вы предоставляете функции вывода способ «извлекать» данные только тогда, когда это необходимо. В нашем случае это гораздо эффективнее, но не так гибко. Генераторы один путь, один проход; данные из файла журнала, который мы прочитали, немедленно удаляются, поэтому мы не можем вернуться к предыдущей строке. С другой стороны, нам не нужно беспокоиться о сохранении данных, как только мы закончим с ними.

44 голосов
/ 10 октября 2008

Преимущество выражения генератора заключается в том, что оно использует меньше памяти, поскольку не создает весь список сразу. Выражения генератора лучше всего использовать, когда список является промежуточным, например, суммируя результаты или создавая из результатов выборку.

Например:

sum(x*2 for x in xrange(256))

dict( ((k, some_func(k) for k in some_list_of_keys) )

Преимущество состоит в том, что список генерируется не полностью, и поэтому используется мало памяти (и также должно быть быстрее)

Тем не менее, вы должны использовать списочные выражения, когда желаемым конечным продуктом является список. Вы не собираетесь сохранять какую-либо память, используя выражения генератора, так как вам нужен сгенерированный список. Вы также получаете возможность использовать любые функции списка, например отсортированные или перевернутые.

Например:

reversed( [x*2 for x in xrange(256)] )
10 голосов
/ 13 марта 2016

При создании генератора из изменяемого объекта (например, списка) следует помнить, что генератор будет оцениваться по состоянию списка во время использования генератора, а не во время создания генератора:

>>> mylist = ["a", "b", "c"]
>>> gen = (elem + "1" for elem in mylist)
>>> mylist.clear()
>>> for x in gen: print (x)
# nothing

Если есть вероятность, что ваш список будет изменен (или изменяемый объект внутри этого списка), но вам нужно состояние при создании генератора, вам нужно вместо этого использовать понимание списка.

4 голосов
/ 04 января 2016

Я использую Модуль мясного фарша Hadoop . Я думаю, что это отличный пример, чтобы принять к сведению:

import mincemeat

def mapfn(k,v):
    for w in v:
        yield 'sum',w
        #yield 'count',1


def reducefn(k,v): 
    r1=sum(v)
    r2=len(v)
    print r2
    m=r1/r2
    std=0
    for i in range(r2):
       std+=pow(abs(v[i]-m),2)  
    res=pow((std/r2),0.5)
    return r1,r2,res

Здесь генератор извлекает числа из текстового файла (размером до 15 ГБ) и применяет к этим числам простую математику, используя Hadoop map-Reduce. Если бы я не использовал функцию yield, а вместо понимания списка, вычисление сумм и среднего значения заняло бы гораздо больше времени (не говоря уже о сложности пространства).

Hadoop - отличный пример использования всех преимуществ Генераторов.

3 голосов
/ 10 сентября 2008

Иногда вы можете обойтись без функции tee из itertools , она возвращает несколько итераторов для одного и того же генератора, который можно использовать независимо.

0 голосов
/ 16 марта 2019

как насчет использования [(exp для x in iter)], чтобы получить пользу от обоих. Производительность от понимания генератора, а также методы списка

...