Когда не подходящее время для использования генераторов python?
Это скорее обратное к Что вы можете использовать для функций генератора Python для?: генераторы python, генераторные выражения и модуль itertools
являются некоторыми из моих любимых функции python в эти дни. Они особенно полезны при настройке цепей операций для работы на большой куче данных - я часто использую их при обработке файлов DSV.
Итак, когда не подходящее время для использования генератора или выражения генератора или функции itertools
?
- Когда я предпочитаю
zip()
над itertools.izip()
или
-
range()
над xrange()
или
-
[x for x in foo]
над (x for x in foo)
?
Очевидно, что нам в конечном итоге нужно "разрешить" генератор в фактические данные, как правило, путем создания списка или итерации по нему с помощью негенераторного цикла. Иногда нам просто нужно знать длину. Это не то, о чем я прошу.
Мы используем генераторы, чтобы мы не назначали новые списки в память для промежуточных данных. Это особенно важно для больших наборов данных. Это имеет смысл и для небольших наборов данных? Есть ли заметный компромисс памяти/процессора?
Мне особенно интересно, если кто-то проделал какое-то профилирование по этому поводу, в свете открывающего обсуждения для определения понимания списка по сравнению с map() и фильтр(). (alt link)
Ответы
Ответ 1
Используйте список вместо генератора, когда:
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, который иногда не может оптимизировать код генератора настолько, насколько это возможно, при обычных вызовах функций и манипуляциях с списками.
Ответ 2
В общем случае не используйте генератор, когда вам нужны операции с списком, такие как len(), reverseed() и т.д.
Также могут возникать ситуации, когда вам не нужна ленивая оценка (например, делать все вычисления спереди, чтобы вы могли освободить ресурс). В этом случае выражение списка может быть лучше.
Ответ 3
Профиль, профиль, профиль.
Профилирование вашего кода - это единственный способ узнать, имеет ли значение то, что вы делаете.
Большинство применений xrange, генераторов и т.д. имеют более статический размер, небольшие наборы данных. Это только когда вы попадаете в большие наборы данных, что это действительно имеет значение. range() vs. xrange() - это в основном только то, что код выглядит чуть-чуть более уродливым, не теряя ничего и, возможно, получая что-то.
Профиль, профиль, профиль.
Ответ 4
Вы не должны опираться на zip
над izip
, range
через xrange
, или перечислить понимание по соображениям генератора. В Python 3.0 range
имеет xrange
-подобную семантику, а zip
имеет izip
-подобную семантику.
Учет списков на самом деле более ясный, например list(frob(x) for x in foo)
для тех случаев, когда вам нужен фактический список.
Ответ 5
Как вы говорите: "Это особенно важно для больших наборов данных", я думаю, что это отвечает на ваш вопрос.
Если вы не нанесете никаких ударов по стенам, я буду придерживаться списков и стандартных функций. Затем, когда вы сталкиваетесь с проблемами с производительностью, сделайте переключатель.
Ответ 6
Что касается производительности: при использовании 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
Ответ 7
Что касается производительности, я не могу думать о том, что вы хотите использовать список по генератору.
Ответ 8
Я никогда не встречал ситуации, когда генераторы будут мешать тому, что вы пытаетесь сделать. Тем не менее, существует множество случаев, когда использование генераторов не поможет вам больше, чем не использовать их.
Например:
sorted(xrange(5))
Не предлагает улучшения:
sorted(range(5))
Ответ 9
Вы должны предпочесть понимание списков, если вам нужно сохранить значения для чего-то еще позже, а размер вашего набора не слишком велик.
Например:
вы создаете список, который будет выполняться несколько раз позже в вашей программе.
В какой-то степени вы можете думать о генераторах в качестве замены для итераций (циклов) против представления списков как типа инициализации структуры данных. Если вы хотите сохранить структуру данных, используйте методы списка.