Выражения генератора и понимание списков

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

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

Итак, вы пытаетесь начать с написания понимания списка:

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, который, в свою очередь, считывает файл.

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

Ответ 5

Преимущество выражения генератора заключается в том, что он использует меньше памяти, поскольку он не создает весь список сразу. Выражения генератора лучше всего использовать, когда список является посредником, например, суммируя результаты или создавая 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)] )

Ответ 6

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

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

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

Ответ 7

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

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. Если бы я не использовал функцию доходности, а вместо этого понимал список, потребовалось бы гораздо больше времени, вычисляя суммы и средние значения (не говоря уже о сложности пространства).

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

Ответ 8

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

Ответ 9

def infinite():
    i = 0
    while(True):
        yield i
        i += 1

for i in infinite():
    print(i)

Теперь попробуйте это со списком!