Генераторные выражения против.Понимание списка

StackOverflow https://stackoverflow.com/questions/47789

Вопрос

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

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

# List comprehension
[x*2 for x in range(256)]
Это было полезно?

Решение

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

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

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

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

Например:

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top