Когда не время использовать генераторы Python? - PullRequest
75 голосов
/ 29 октября 2008

Это скорее обратное значение Для чего вы можете использовать функции генератора Python? : генераторы python, выражения генератора и модуль itertools являются одними из моих любимых функций python в наши дни. Они особенно полезны при настройке цепочек операций для выполнения с большой кучей данных - я часто использую их при обработке файлов DSV.

Так когда же не хорошее время для использования генератора, или выражения генератора, или функции itertools?

  • Когда я предпочитаю zip(), а не itertools.izip() или
  • range() свыше xrange() или
  • [x for x in foo] свыше (x for x in foo)?

Очевидно, что в конечном итоге нам необходимо «преобразовать» генератор в реальные данные, обычно путем создания списка или итерации по нему с помощью цикла без генератора. Иногда нам просто нужно знать длину. Я не об этом спрашиваю.

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

Мне особенно интересно, если кто-нибудь выполнил какое-либо профилирование по этому вопросу, в свете откровенного обсуждения производительности понимания списка по сравнению с map () и filter () . ( альтернативная ссылка )

Ответы [ 9 ]

49 голосов
/ 29 октября 2014

Использовать список вместо генератора, когда:

1) Вам необходимо получить доступ к данным несколько раз раз (т.е. кэшировать результаты, а не пересчитывать их):

for i in outer:           # used once, okay to be a generator or return a list
    for j in inner:       # used multiple times, reusing a list is better
         ...

2) Вам необходим произвольный доступ (или любой другой доступ, кроме прямого последовательного порядка):

for i in reversed(data): ...     # generators aren't reversible

s[i], s[j] = s[j], s[i]          # generators aren't indexable

3) Вам необходимо объединить строк (для этого требуется два прохода по данным):

s = ''.join(data)                # lists are faster than generators in this use case

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

39 голосов
/ 29 октября 2008

Как правило, не используйте генератор, когда вам нужны операции со списками, такие как len (), reversed () и т. Д.

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

24 голосов
/ 29 октября 2008

Профиль, Профиль, Профиль.

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

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

Профиль, Профиль, Профиль.

17 голосов
/ 29 октября 2008

Вы никогда не должны отдавать предпочтение zip над izip, range над xrange или составлять списки по сравнению с генераторами. В Python 3.0 range имеет xrange -подобную семантику, а zip имеет izip -подобную семантику.

Понимание списка на самом деле яснее, чем list(frob(x) for x in foo) для тех случаев, когда вам нужен фактический список.

6 голосов
/ 01 ноября 2008

Относительно производительности: при использовании psyco списки могут быть немного быстрее, чем генераторы. В приведенном ниже примере списки работают почти на 50% быстрее при использовании psyco.full ()

import psyco
import time
import cStringIO

def time_func(func):
    """The amount of time it requires func to run"""
    start = time.clock()
    func()
    return time.clock() - start

def fizzbuzz(num):
    """That algorithm we all know and love"""
    if not num % 3 and not num % 5:
        return "%d fizz buzz" % num
    elif not num % 3:
        return "%d fizz" % num
    elif not num % 5:
        return "%d buzz" % num
    return None

def with_list(num):
    """Try getting fizzbuzz with a list comprehension and range"""
    out = cStringIO.StringIO()
    for fibby in [fizzbuzz(x) for x in range(1, num) if fizzbuzz(x)]:
        print >> out, fibby
    return out.getvalue()

def with_genx(num):
    """Try getting fizzbuzz with generator expression and xrange"""
    out = cStringIO.StringIO()
    for fibby in (fizzbuzz(x) for x in xrange(1, num) if fizzbuzz(x)):
        print >> out, fibby
    return out.getvalue()

def main():
    """
    Test speed of generator expressions versus list comprehensions,
    with and without psyco.
    """

    #our variables
    nums = [10000, 100000]
    funcs = [with_list, with_genx]

    #  try without psyco 1st
    print "without psyco"
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

    #  now with psyco
    print "with psyco"
    psyco.full()
    for num in nums:
        print "  number:", num
        for func in funcs:
            print func.__name__, time_func(lambda : func(num)), "seconds"
        print

if __name__ == "__main__":
    main()

Результаты:

without psyco
  number: 10000
with_list 0.0519102208309 seconds
with_genx 0.0535933367509 seconds

  number: 100000
with_list 0.542204280744 seconds
with_genx 0.557837353115 seconds

with psyco
  number: 10000
with_list 0.0286369007033 seconds
with_genx 0.0513424889137 seconds

  number: 100000
with_list 0.335414877839 seconds
with_genx 0.580363490491 seconds
6 голосов
/ 29 октября 2008

Как вы упоминаете, "Это особенно важно для больших наборов данных", я думаю, что это отвечает на ваш вопрос.

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

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

3 голосов
/ 02 ноября 2008

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

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

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

3 голосов
/ 29 октября 2008

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

Например:

sorted(xrange(5))

Не предлагает улучшения по сравнению с:

sorted(range(5))
3 голосов
/ 29 октября 2008

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

...