生成器表达式与列表理解

Rea*_*nly 390 python list-comprehension generator

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

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

# List comprehension
[x*2 for x in range(256)]
Run Code Online (Sandbox Code Playgroud)

Eli*_*ght 266

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

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

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

  • 有时您**使用生成器 - 例如,如果您使用yield编写协同调度协程.但如果你这样做,你可能不会问这个问题;) (67认同)
  • 我知道这是旧的,但我认为值得注意的是,生成器(以及任何可迭代的)可以添加到带有扩展名的列表中:`a = [1,2,3] b = [4,5,6] a.extend( b)` - 现在将是[1,2,3,4,5,6].(你能在评论中添加新行吗?) (12认同)
  • @jarvisteve你的例子掩盖了你所说的话.这里还有一个很好的观点.列表可以用生成器扩展,但是没有必要使它成为生成器.生成器不能使用列表进行扩展,而生成器也不是可迭代的.`a =(x表示范围(0,10)中的x),b = [1,2,3]`.`a.extend(b)`抛出异常.`b.extend(a)`将评估所有a,在这种情况下,首先使它成为一个发生器是没有意义的. (12认同)
  • @SlaterTyranus你是100%正确的,我赞成你的准确性.尽管如此,我认为他的评论对于OP的问题是一个有用的非答案,因为它会帮助那些发现自己的人,因为他们在搜索引擎中输入了"将生成器与列表理解相结合"之类的东西. (4认同)
  • 使用生成器迭代一次的原因(例如*我对内存不足的担忧覆盖了我对一次“获取”一个值的担忧*)在迭代多次时是否仍然适用?我会说它可能会使列表更有用,但这是否足以超过内存问题是另一回事。 (2认同)

dF.*_*dF. 170

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

  • +1为无限.无论您对表现的关注程度如何,您都无法使用列表. (36认同)
  • @Annan只有你已经有权访问另一个无限的发电机.例如,`itertools.count(n)`是一个无限的整数序列,从n开始,所以`(在itertools.count(n)中的项目为2**项)`将是一个无限的权力序列. 2`从'2**n`开始. (5认同)
  • 生成器在迭代后从内存中删除项目.例如,如果您只想显示大数据,那么速度很快.它不是记忆猪.发电机项目按"需要"处理.如果你想挂在列表上或再次迭代它(所以存储项目)然后使用列表理解. (2认同)

Joh*_*kin 93

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

  • @GuillermoAres这是"谷歌搜索"对于最重要的意义的直接结果:*比其他任何事情更重要; 最高.* (6认同)
  • 最好说列表推导在范围较小时更快,但是随着规模的增加,动态计算值变得更有价值——正好可以及时使用。这就是生成器表达式的作用。 (5认同)
  • 那么`lists` 比`generator` 表达式更快吗?通过阅读 dF 的回答,发现情况正好相反。 (3认同)
  • “速度至上”也让我困惑。其他[最近的答案证实(使用“%timeit”基准)](/sf/answers/4389682391/)这意味着什么 - **列表理解*稍微*比生成器**快(如@HassanBaig 说,这不是我所期望的)。但是列表理解*内存消耗*(使用“%memit”)**比生成器高得多**。 (3认同)
  • 这可能会有点偏离主题,但是不幸的是“无法理解”……在这种情况下,“最重要的”是什么意思?我不是说英语的人... :) (2认同)

tyl*_*erl 57

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

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

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

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

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

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

logfile = open("hugefile.txt","r")
entry_lines = ((line,len(line)) for line in logfile if line.startswith("ENTRY"))
Run Code Online (Sandbox Code Playgroud)

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

long_entries = ((line,length) for (line,length) in entry_lines if length > 80)
Run Code Online (Sandbox Code Playgroud)

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

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

outfile = open("filtered.txt","a")
for entry,length in long_entries:
    outfile.write(entry)
Run Code Online (Sandbox Code Playgroud)

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

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


小智 45

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

例如:

sum(x*2 for x in xrange(256))

dict( ((k, some_func(k) for k in some_list_of_keys) )
Run Code Online (Sandbox Code Playgroud)

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

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

例如:

reversed( [x*2 for x in xrange(256)] )
Run Code Online (Sandbox Code Playgroud)

  • 在语言中有一个提示,即生成器表达式意味着以这种方式使用.丢了括号!`sum(x*2代表x in xrange(256))` (9认同)
  • `sorted`和`reversed`在包含的任何可迭代的生成器表达式上都能正常工作. (7认同)
  • 这应该是`dict((k,some_func(k))for some_list_of_keys中的k).当然2.7+ dict/set理解语法甚至更甜. (3认同)

kev*_*und 19

Python 3.7:

列表理解速度更快。

在此输入图像描述

生成器的内存效率更高。 在此输入图像描述

正如所有其他人所说,如果您想要扩展无限数据,您最终将需要一个生成器。对于需要速度的相对静态的中小型工作,列表理解是最好的。

  • 事情没那么简单。列表比较仅在某些情况下更快。如果您使用“any”并且预计会出现早期的“False”元素,则生成器可以对列表理解产生重大改进。但如果两者都被耗尽,那么列表比较通常会更快。您确实需要[分析应用程序并查看](https://ericlippert.com/2012/12/17/performance-rant/)。 (9认同)

fre*_*ker 12

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

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

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

  • 这应该是公认的答案。如果您的数据大于可用内存,您应该始终使用生成器,尽管循环遍历内存中的列表可能会更快(但您没有足够的内存来执行此操作)。 (2认同)