89

这与您可以使用 Python 生成器函数做什么?: python 生成器、生成器表达式和itertools模块是我最近最喜欢的 python 功能。它们在设置操作链以对大量数据执行时特别有用——我经常在处理 DSV 文件时使用它们。

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

  • 我应该什么时候更喜欢zip()itertools.izip()或者
  • range()超过xrange(), 或
  • [x for x in foo]结束了(x for x in foo)

显然,我们最终需要将生成器“解析”为实际数据,通常是通过创建一个列表或使用非生成器循环对其进行迭代。有时我们只需要知道长度。这不是我要问的。

我们使用生成器,这样我们就不会为临时数据分配新列表到内存中。这对于大型数据集尤其有意义。它对小型数据集也有意义吗?是否存在明显的内存/cpu 权衡?

鉴于列表理解性能与 map() 和 filter()的令人大开眼界的讨论,如果有人对此进行了一些分析,我特别感兴趣。(替代链接

4

10 回答 10

62

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

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,它有时无法像普通函数调用和列表操作那样优化生成器代码。

于 2014-10-29T16:36:57.670 回答
42

一般来说,当你需要列表操作时不要使用生成器,比如 len()、reversed() 等。

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

于 2008-10-29T04:42:05.870 回答
26

简介,简介,简介。

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

xrange、生成器等的大多数用法都超过了静态大小的小数据集。只有当您处理大型数据集时,它才会真正发挥作用。range() 与 xrange() 主要只是让代码看起来更丑一点,并且不会丢失任何东西,并且可能会获得一些东西。

简介,简介,简介。

于 2008-10-29T11:37:31.913 回答
17

您永远不应该偏爱zipover iziprangeoverxrange或 list 推导而不是生成器推导。在 Python 3.0中,rangexrange-like 语义和-like 语义。zipizip

列表推导实际上更清晰,就像list(frob(x) for x in foo)您需要一个实际列表的时候一样。

于 2008-10-29T04:28:58.643 回答
7

正如您提到的,“这对大型数据集尤其有意义”,我认为这回答了您的问题。

如果您在性能方面没有遇到任何问题,您仍然可以坚持使用列表和标准功能。然后,当您遇到性能问题时,请进行切换。

然而,正如@u0b34a0f6ae 在评论中提到的那样,在开始时使用生成器可以让您更轻松地扩展到更大的数据集。

于 2008-10-29T08:50:47.117 回答
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()

结果:

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
于 2008-11-01T05:53:31.667 回答
3

我从未发现发电机会阻碍您尝试做的事情的情况。然而,在很多情况下,使用生成器对你没有任何帮助,而不仅仅是不使用它们。

例如:

sorted(xrange(5))

不提供任何改进:

sorted(range(5))
于 2008-10-29T16:44:36.833 回答
3

如果您以后需要保留其他值并且集合的大小不太大,您应该更喜欢列表推导式。

例如:您正在创建一个列表,稍后您将在程序中循环多次。

在某种程度上,您可以将生成器视为迭代(循环)的替代品,而不是将列表推导视为一种数据结构初始化。如果要保留数据结构,请使用列表推导。

于 2008-11-01T23:49:09.560 回答
2

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

于 2008-10-29T11:44:06.313 回答
0

生成器构建和可枚举的值列表。当迭代过程可以按需使用值时,可枚举项很有用。构建你的生成器需要时间,所以如果列表是数百万条记录,使用 sql server 处理 sql 中的数据可能更有用。

于 2021-05-27T17:36:53.053 回答