2008-09-06 6 views
307

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

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

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

может `[exp for x in iter]` просто быть сахаром для `list ((exp для x в iter))`? или есть разница в исполнении? – b0fh 2013-01-24 00:32:29

+1

Думаю, у меня был соответствующий вопрос, поэтому при использовании урона мы можем использовать только выражение генератора из функции или нам нужно использовать выход для функции для возврата объекта генератора? – 2013-09-06 13:30:16

+17

@ b0fh Очень поздний ответ на ваш комментарий: в Python2 есть крошечная разница, переменная цикла просачивается из понимания списка, в то время как выражение генератора не будет течь. Сравните `X = [x ** 2 для x в диапазоне (5)]; print x` с `Y = список (y ** 2 для y в диапазоне (5)); print y`, второй даст ошибку. В Python3 понимание по спискам действительно является синтаксическим сахаром для выражения генератора, поданного в `list()`, как вы ожидали, поэтому переменная цикла будет [больше не просачиваться] (https://www.python.org/dev/ Peps/PEP-0289 # на-детали). – 2014-11-16 19:53:52

ответ

219

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

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 

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

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

73

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

133

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

3

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

40

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

Например:

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

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

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

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

Например:

reversed([x*2 for x in xrange(256)]) 
43

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

Представьте, что у вас есть файл журнала 2TB под названием «огромный файл.txt», и вы хотите, чтобы содержание и длина для всех строк начинались со слова «ВХОД».

Так вы попробуйте начать путем написания списка понимания:

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 символов. И, в свою очередь, генераторзапрашивает строки (отфильтрованные как указано) от итератора logfile, который, в свою очередь, считывает файл.

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

4

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

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 

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

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

9

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

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

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