生成器expression式与列表理解
什么时候应该使用生成器expression式,什么时候应该在Python中使用列表推导?
# Generator expression (x*2 for x in range(256)) # List comprehension [x*2 for x in range(256)]
John的答案是好的(当你想多次迭代的时候,列表的parsing更好)。 但是,值得注意的是,如果您想使用任何列表方法,则应该使用列表。 例如,下面的代码将不起作用:
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
基本上,如果你正在做的迭代一次,使用一个生成器expression式。 如果你想存储和使用生成的结果,那么你可能会更好的清单理解。
因为performance是select一个最常见的理由,所以我的build议是不要担心,只要select一个; 如果你发现你的程序运行速度太慢,那么只有这样,你才应该回头担心调整你的代码。
遍历生成器expression式或列表理解将执行相同的操作。 然而, 列表理解将首先在内存中创build整个列表,而生成器expression式将在运行中创build项目,因此您可以将它用于非常大的(也是无限的)序列。
当结果需要迭代多次或速度最重要时,使用列表推导。 使用范围大或无限的生成器expression式。
生成器expression式的好处是它使用更less的内存,因为它不会一次构build整个列表。 生成器expression式最适用于列表是中介的情况,如汇总结果,或者在结果中创build一个字典。
例如:
sum(x*2 for x in xrange(256)) dict( ((k, some_func(k) for k in some_list_of_keys) )
这样做的好处是列表不是完全生成的,因此使用的内存很less(也应该更快)
但是,当所需的最终产品是列表时,您应该使用列表parsing。 你不会使用生成器expression式来保存任何memeory,因为你想要生成的列表。 您还可以使用任何列表function(如sorting或反转)的好处。
例如:
reversed( [x*2 for x in xrange(256)] )
重要的一点是,列表理解创build一个新的列表。 生成器创build一个可迭代的对象,当你消耗这些位时,它将即时“过滤”源材料。
假设你有一个名为“hugefile.txt”的2TB日志文件,你需要所有以“ENTRY”开头的行的内容和长度。
所以你试着写一个列表理解开始:
logfile = open("hugefile.txt","r") entry_lines = [(line,len(line)) for line in logfile if line.startswith("ENTRY")]
这样会把整个文件处理好,处理每一行,并把匹配的行存储在你的数组中。 因此这个数组可以包含高达2TB的内容。 这是很多内存,可能不适合你的目的。
因此,我们可以使用生成器来为我们的内容应用“filter”。 在我们开始迭代结果之前,实际上没有数据被读取。
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)
现在我们读取input文件。 由于for
循环继续请求附加行, long_entries
生成器要求来自entry_lines
生成器的行,只返回长度大于80个字符的行。 然后, entry_lines
生成器从logfile
迭代器请求行(如指示的那样进行过滤),然后读取该文件。
因此,不是以完全填充的列表的forms将数据“推送”到输出函数,而是给输出函数一种只在需要时才“拉”数据的方式。 这在我们的情况下效率更高,但不够灵活。 发电机是一种方式,一个传递; 我们读取的日志文件中的数据立即被丢弃,所以我们不能回到上一行。 另一方面,一旦我们完成了数据,我们不必担心数据的保留。
从可变对象(如列表)创build生成器时,请注意生成器在使用生成器时将在列表的状态下进行评估,而不是在创build生成器时进行评估:
>>> mylist = ["a", "b", "c"] >>> gen = (elem + "1" for elem in mylist) >>> mylist.clear() >>> for x in gen: print (x) # nothing
如果你的列表有可能被修改(或者列表中的可变对象),但是你需要创build生成器的状态,那么你需要使用列表理解。
有时你可以从itertools中得到tee函数,它可以为独立使用的同一个生成器返回多个迭代器。
我正在使用Hadoop Mincemeat模块 。 我认为这是一个很好的例子,需要注意的是:
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对这些数字应用简单的math运算。 如果我没有使用收益函数,而是使用列表理解,那么计算总和和平均值(更不用说空间复杂度)将花费更长的时间。
Hadoop是利用发生器的所有优点的一个很好的例子。