When is not a good time to use python generators?
这与使用Python生成器函数的目的相反。:python生成器、生成器表达式和
那么,什么时候不适合使用生成器、生成器表达式或
- 我应该什么时候选择
zip() 而不是itertools.izip() ,或者 range() 超过xrange() ,或[x for x in foo] 比(x for x in foo) 高?
显然,我们最终需要将生成器"解析"为实际数据,通常通过创建列表或使用非生成器循环对其进行迭代来实现。有时我们只需要知道长度。这不是我要的。
我们使用生成器,这样就不会在内存中为临时数据分配新的列表。这对于大型数据集尤其有意义。它对小数据集也有意义吗?是否存在明显的内存/CPU权衡?
我特别感兴趣的是,如果有人对此做了一些分析,根据关于列表理解性能与map()和filter()的令人大开眼界的讨论。(alt链接)
在以下情况下使用列表而不是生成器:
1)您需要多次访问数据(即缓存结果而不是重新计算结果):
1 2 3 | 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)您需要随机访问(或除正向顺序之外的任何访问):
1 2 3 | for i in reversed(data): ... # generators aren't reversible s[i], s[j] = s[j], s[i] # generators aren't indexable |
。
3)您需要连接字符串(这需要对数据进行两次传递):
1 | s = ''.join(data) # lists are faster than generators in this use case |
。
4)您使用的Pypy有时无法在常规函数调用和列表操作中尽可能优化生成器代码。
一般来说,当需要列表操作时,不要使用生成器,比如len()、reversed()等等。
有时您可能不希望进行懒惰的评估(例如,提前进行所有计算,以便释放资源)。在这种情况下,列表表达式可能更好。
外形,外形,外形。
分析您的代码是了解您所做的操作是否有任何效果的唯一方法。
xrange、generator等的大多数用法都是静态大小的小数据集。只有当你接触到大数据集时,它才真正起到作用。range()与xrange()的比较主要是让代码看起来有点难看,而不是丢失任何东西,或者获得一些东西。
外形,外形,外形。
你不应该偏爱
清单的理解实际上更清晰,就像
关于性能:如果使用psyco,列表可能比生成器快得多。在下面的示例中,使用psyco.full()时,列表速度快了近50%。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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" # 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" if __name__ =="__main__": main() |
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
。
正如你所提到的,"这对大型数据集特别有意义",我认为这回答了你的问题。
如果您没有遇到任何障碍,从性能上讲,您仍然可以坚持使用列表和标准功能。然后,当您遇到性能问题时,进行切换。
正如@u0b34a0f6ae在注释中提到的,但是,在开始时使用生成器可以使您更容易地扩展到更大的数据集。
如果以后需要保留其他内容的值,并且集合的大小不太大,那么您应该更喜欢列表理解。
例如:您正在创建一个列表,稍后将在程序中循环多次。
在某种程度上,您可以将生成器视为迭代(循环)与列表理解的替换,而将其视为数据结构初始化的一种类型。如果要保留数据结构,请使用列表理解。
我从来没有发现发电机会阻碍你的工作。然而,在很多情况下,使用生成器除了不使用它们之外,对您没有任何帮助。
例如:
1 | sorted(xrange(5)) |
在以下方面没有任何改进:
1 | sorted(range(5)) |
号
就性能而言,我想不出任何时候您会希望在生成器上使用列表。