什么时候不是使用python生成器的好时机?

Dav*_*Eyk 79 python optimization iterator generator

这与你可以使用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链接)

Ray*_*ger 53

在以下情况下使用列表而不是生成器:

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
         ...
Run Code Online (Sandbox Code Playgroud)

2)您需要随机访问(或除前向顺序之外的任何访问):

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

s[i], s[j] = s[j], s[i]          # generators aren't indexable
Run Code Online (Sandbox Code Playgroud)

3)您需要连接字符串(需要两次传递数据):

s = ''.join(data)                # lists are faster than generators in this use case
Run Code Online (Sandbox Code Playgroud)

4)您正在使用PyPy,有时无法通过正常的函数调用和列表操作来优化生成器代码.

  • @DavidEyk*str.join*进行一次传递以累加所有字符串片段的长度,因此它知道要为组合的最终结果分配的大量内存.第二遍将字符串片段复制到新缓冲区中以创建单个新字符串.请参阅https://hg.python.org/cpython/file/82fd95c2851b/Objects/stringlib/join.h#l54 (5认同)
  • @ ikaros45如果*join*的输入不是列表,则必须做额外的工作来为两次传递构建一个临时列表.大概这个``data = data if ifinstance(data,list)else list(data); n = sum(map(len,data)); buffer = bytearray(n); ... <将片段复制到缓冲区>```. (4认同)
  • 有趣的是,我经常使用生成器来连接环。但是,我想知道,如果需要两次通过,它如何工作?例如 `''.join('%s' % i for i in xrange(10))` (2认同)

Rya*_*rom 39

通常,在需要列表操作时不要使用生成器,例如len(),reversed()等.

有时您可能不想进行延迟评估(例如,预先进行所有计算以便释放资源).在这种情况下,列表表达式可能会更好.

  • 此外,预先进行所有计算可确保如果列表元素的计算引发异常,则会在列表被创建*的位置抛出,而不是在随后迭代它的循环中抛出.如果你需要在继续之前确保整个列表没有错误处理,那么生成器就不好了. (25认同)
  • 那是个很好的观点.在处理发电机的过程中,只是让一切都爆炸,这是非常令人沮丧的.它可能是危险的. (4认同)

Jer*_*rub 25

简介,简介,简介.

分析代码是了解您所做的事情是否有任何影响的唯一方法.

xrange,generator等的大多数用法都是静态大小的小数据集.只有当你到达大型数据集时才会真正发挥作用.range()vs. xrange()主要是让代码看起来更丑陋,不会丢失任何东西,也许会获得一些东西.

简介,简介,简介.

  • 简介,简介,简介。我完全同意。简介,简介,简介。 (2认同)

Ste*_*wig 17

你不应该偏向zipizip,rangexrange,或者在发电机链表推导.在Python 3.0 rangexrange样的语义和zipizip样的语义.

列表理解实际上更清晰,就像list(frob(x) for x in foo)那些需要实际列表的时候一样.

  • 我明白你的意思,但我觉得`[]`形式足够描述(而且更简洁,一般不那么混乱).但这只是一个品味问题. (8认同)
  • 对于小数据大小,列表操作更快,但*数据大小很小时,*所有*都很快,因此除非您有特定的理由使用列表,否则您应该总是更喜欢生成器(出于这些原因,请参阅Ryan Ginstrom的答案). (4认同)
  • @Steven我不同意,但我想知道你的答案背后的原因是什么.为什么zip,范围和列表推导永远不会比相应的"懒惰"版本更受青睐? (3认同)

mon*_*kut 7

正如你所提到的,"这对于大型数据集特别有意义",我认为这可以回答你的问题.

如果你没有在性能方面击中任何墙壁,你仍然可以坚持列表和标准功能.然后当遇到性能问题时进行切换.

正如评论中@ u0b34a0f6ae所提到的,在开始时使用生成器可以使您更容易扩展到更大的数据集.

  • +1 Generators使您的代码更适合大数据集,而无需您预测它. (4认同)

Rya*_*rom 6

关于性能:如果使用psyco,列表可以比生成器快得多.在下面的示例中,使用psyco.full()时,列表的速度提高了近50%

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()
Run Code Online (Sandbox Code Playgroud)

结果:

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
Run Code Online (Sandbox Code Playgroud)

  • 此外,psyco现在几乎没有维护.所有的开发人员都花时间在PyPy的JIT上,这是我所知道的优化生成器. (4认同)