代码之家  ›  专栏  ›  技术社区  ›  David Eyk

什么时候不适合使用Python生成器?

  •  72
  • David Eyk  · 技术社区  · 17 年前

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

    那么什么时候呢? 使用生成器、生成器表达式或 迭代工具 功能?

    • 我更喜欢什么时候 zip() 结束 itertools.izip()
    • range() 结束 xrange()
    • [x for x in foo] 结束 (x for x in foo) ?

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

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

    我特别感兴趣的是,如果有人对此做了一些分析,鉴于 list comprehension performance vs. map() and filter() . ( alt link )

    9 回复  |  直到 8 年前
        1
  •  47
  •   Raymond Hettinger    11 年前

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

    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)您正在使用 派皮 在常规的函数调用和列表操作中,它有时无法尽可能地优化生成器代码。

        2
  •  38
  •   Ryan Ginstrom    17 年前

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

    有时,您可能不希望进行懒惰的评估(例如,提前进行所有计算,以便释放资源)。在这种情况下,列表表达式可能更好。

        3
  •  23
  •   Jerub    17 年前

    外形,外形,外形。

    分析您的代码是了解您所做的操作是否有任何效果的唯一方法。

    xrange、generator等的大多数用法都是静态大小的小数据集。只有当你接触到大数据集时,它才真正起到作用。range()与xrange()的比较主要是让代码看起来更难看一点,而不是丢失任何东西,或者获得一些东西。

    外形,外形,外形。

        4
  •  17
  •   Uli Köhler    12 年前

    你不应该偏袒 zip 结束 izip , range 结束 xrange 或列出对生成器理解的理解。在Python 3中 范围 润智 -像语义学和 拉链 伊扎普 -像语义学。

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

        5
  •  6
  •   user3666197    9 年前

    关于性能:如果使用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
    
        6
  •  6
  •   monkut    8 年前

    正如你所提到的,“这对大型数据集特别有意义”,我认为这回答了你的问题。

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

    正如@u0b34a0f6ae在注释中提到的,但是,在开始时使用生成器可以使您更容易地扩展到更大的数据集。

        7
  •  3
  •   minty    16 年前

    如果以后需要保留其他内容的值,并且集合的大小不太大,那么您应该更喜欢列表理解。

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

    在某种程度上,您可以将生成器视为迭代(循环)与列表理解的替换,而将其视为数据结构初始化的一种类型。如果要保留数据结构,请使用列表理解。

        8
  •  2
  •   Jason Baker    17 年前

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

        9
  •  2
  •   Jeremy Cantrell    17 年前

    我从来没有发现发电机会阻碍你的工作。然而,在很多情况下,使用生成器除了不使用它们之外,对您没有任何帮助。

    例如:

    sorted(xrange(5))
    

    在以下方面没有任何改进:

    sorted(range(5))