关于优化:什么时候不是使用python生成器的好时机?

When is not a good time to use python generators?

这与使用Python生成器函数的目的相反。:python生成器、生成器表达式和itertools模块是我最近最喜欢的python特性之一。它们在设置操作链以在一大堆数据上执行时特别有用——我在处理DSV文件时经常使用它们。

那么,什么时候不适合使用生成器、生成器表达式或itertools函数呢?

  • 我应该什么时候选择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()的比较主要是让代码看起来有点难看,而不是丢失任何东西,或者获得一些东西。

外形,外形,外形。


你不应该偏爱zip而不是iziprange而不是xrange,或者列出理解而不是生成器理解。在python 3.0中,range具有xrange类语义,zip具有izip类语义。

清单的理解实际上更清晰,就像list(frob(x) for x in foo)那样,在那些时候你需要一个实际的清单。


关于性能:如果使用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"
        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()

结果:

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


就性能而言,我想不出任何时候您会希望在生成器上使用列表。