首页 文章

生成器表达式与列表理解

提问于
浏览
350

什么时候应该使用生成器表达式?什么时候应该在Python中使用列表推导?

# Generator expression
(x*2 for x in range(256))

# List comprehension
[x*2 for x in range(256)]

8 回答

  • 51

    有时您可以使用itertools中的tee函数,它返回可以单独使用的同一生成器的多个迭代器 .

  • 9

    John的答案很好(当你想多次迭代某些事情时,列表理解会更好) . 但是,如果要使用任何列表方法,还应该注意使用列表 . 例如,以下代码将不起作用:

    def gen():
        return (something for something in get_some_stuff())
    
    print gen()[:2]     # generators don't support indexing or slicing
    print [5,6] + gen() # generators can't be added to lists
    

    基本上,如果您所做的只是迭代一次,请使用生成器表达式 . 如果你想存储和使用生成的结果,那么你最好使用列表理解 .

    由于性能是选择其中一种的最常见原因,我的建议是不要担心它而只选择一种;如果您发现您的程序运行速度太慢,那么只有这样您才能回过头来担心调整代码 .

  • 44

    迭代生成器表达式或列表理解将执行相同的操作 . 但是,列表推导将首先在内存中创建整个列表,而生成器表达式将动态创建项目,因此您可以将它用于非常大(也是无限!)的序列 .

  • 239

    当结果需要多次迭代或速度至关重要时,请使用列表推导 . 使用范围大或无限的生成器表达式 .

  • 86

    重要的是列表理解创建了一个新列表 . 生成器创建一个可迭代的对象,当您使用这些位时,它将动态地“过滤”源材料 .

    想象一下,你有一个名为“hugefile.txt”的2TB日志文件,你想要所有以“ENTRY”开头的行的内容和长度 .

    所以你尝试通过编写列表理解来开始:

    logfile = open("hugefile.txt","r")
    entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]
    

    这会覆盖整个文件,处理每一行,并将匹配的行存储在您的数组中 . 因此,该阵列最多可包含2TB的内容 . 这是很多内存,可能不适用于您的目的 .

    因此,我们可以使用生成器将“过滤器”应用于我们的内容 . 在我们开始迭代结果之前,实际上没有数据被读取 .

    logfile = open("hugefile.txt","r")
    entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))
    

    甚至还没有从我们的文件中读取过一行 . 事实上,我们想要进一步过滤我们的结果:

    long_entries = ((line,length) for (line,length) in entry_lines if length > 80)
    

    仍然没有读过任何内容,但我们现在已经指定了两个将按照我们的意愿对我们的数据起作用的生成器 .

    让我们将过滤后的行写出到另一个文件:

    outfile = open("filtered.txt","a")
    for entry,length in long_entries:
        outfile.write(entry)
    

    现在我们读取输入文件 . 当我们的 for 循环继续请求额外的行时, long_entries 生成器需要来自 entry_lines 生成器的行,只返回长度大于80个字符的行 . 反过来, entry_lines 生成器从 logfile 迭代器请求行(按指示过滤),然后迭代器读取文件 .

    因此,不是以完全填充列表的形式将数据“推送”到输出函数,而是为输出函数提供一种仅在需要时“拉”数据的方法 . 在我们的情况下,这更有效,但不太灵活 . 发电机是单向的,一次通过;我们读过的日志文件中的数据会被立即丢弃,因此我们无法返回上一行 . 另一方面,我们不必担心一旦完成数据就保持数据 .

  • 5

    生成器表达式的好处是它使用更少的内存,因为它不会立即构建整个列表 . 当列表是中介时,最好使用生成器表达式,例如对结果求和,或者从结果中创建字典 .

    例如:

    sum(x*2 for x in xrange(256))
    
    dict( ((k, some_func(k) for k in some_list_of_keys) )
    

    优点是列表没有完全生成,因此使用的内存很少(也应该更快)

    但是,当所需的最终产品是列表时,您应该使用列表推导 . 您不会使用生成器表达式保存任何内存,因为您需要生成的列表 . 您还可以使用任何列表函数,如已排序或反向 .

    例如:

    reversed( [x*2 for x in xrange(256)] )
    
  • 154

    从可变对象(如列表)创建生成器时,请注意在使用生成器时,生成器将在列表状态上进行评估,而不是在创建生成器时:

    >>> mylist = ["a", "b", "c"]
    >>> gen = (elem + "1" for elem in mylist)
    >>> mylist.clear()
    >>> for x in gen: print (x)
    # nothing
    

    如果您的列表有可能被修改(或该列表中的可变对象),但您需要创建生成器时的状态,则需要使用列表推导 .

  • 3

    我正在使用Hadoop Mincemeat module . 我认为这是一个很好的例子,需要注意:

    import mincemeat
    
    def mapfn(k,v):
        for w in v:
            yield 'sum',w
            #yield 'count',1
    
    
    def reducefn(k,v): 
        r1=sum(v)
        r2=len(v)
        print r2
        m=r1/r2
        std=0
        for i in range(r2):
           std+=pow(abs(v[i]-m),2)  
        res=pow((std/r2),0.5)
        return r1,r2,res
    

    这里的生成器从文本文件(大到15GB)中获取数字,并使用Hadoop的map-reduce对这些数字应用简单的数学运算 . 如果我没有使用yield函数,而是使用列表推导,那么计算总和和平均值会花费更长的时间(更不用说空间复杂度)了 .

    Hadoop是使用Generators的所有优点的一个很好的例子 .

相关问题