Когда неподходящее время для использования генераторов python?

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

Вопрос

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

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

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

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

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

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

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

Что касается производительности:при использовании 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

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

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

Например:

sorted(xrange(5))

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

sorted(range(5))

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

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

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

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