列表理解与lambda +过滤器


857

我碰巧发现自己有一个基本的过滤需求:我有一个列表,并且必须按项目的属性对其进行过滤。

我的代码如下所示:

my_list = [x for x in my_list if x.attribute == value]

但是后来我想,这样写会更好吗?

my_list = filter(lambda x: x.attribute == value, my_list)

它更具可读性,并且如果需要性能,可以将lambda取出以获取收益。

问题是:使用第二种方法是否有警告?有任何性能差异吗?我是否完全想念Pythonic Way™,应该以另一种方式来做到这一点(例如,使用itemgetter而不是lambda)吗?


19
一个更好的例子是您已经有一个命名函数很好地用作谓词的情况。在那种情况下,我想会有更多的人同意这filter更具可读性。当您有一个简单的表达式可以在listcomp中按原样使用,但必须包装在lambda中(或类似地由partialoperator函数构造等)传递给时filter,这就是listcomps获胜的时候。
abarnert

3
应该说,至少在Python3中,返回的filter是过滤器生成器对象而不是列表。
Matteo Ferla,

Answers:


588

奇怪的是,不同的人有多少美丽。我发现列表理解比filter+ 清晰得多lambda,但是请使用任何您更容易理解的列表。

有两件事可能会减慢您对的使用filter

第一个是函数调用开销:使用Python函数(无论是由def还是创建的lambda)后,过滤器的运行速度可能会比列表理解慢。几乎可以肯定,这还不够重要,并且在对代码进行计时并发现它是瓶颈之前,您不应该对性能进行太多的考虑,但是区别仍然存在。

可能适用的其他开销是,lambda被强制访问作用域变量(value)。这比访问局部变量要慢,并且在Python 2.x中,列表推导仅访问局部变量。如果您使用的是Python 3.x,则列表推导是在单独的函数中运行的,因此它也将value通过闭包进行访问,这种区别将不适用。

要考虑的另一个选项是使用生成器而不是列表推导:

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el

然后,在您的主要代码(这才是真正的可读性)中,您已经用有希望的有意义的函数名称替换了列表理解和过滤器。


68
+1为发电机。我在家有一个演示文稿的链接,该演示文稿显示了惊人的发电机。您也可以仅通过将转换[]为来用生成器表达式替换列表推导()。另外,我同意list comp更漂亮。
韦恩·沃纳

1
实际上,没有-过滤器更快。只需使用stackoverflow.com/questions/5998245/…之
skqr 2015年

2
@skqr最好只将timeit用于基准测试,但是请举一个示例,filter使用Python回调函数会更快。
邓肯

8
@ tnq177这是大卫·比斯利(David Beasley)在发电机上的演讲-dabeazaz.com/generators
韦恩·沃纳

2
@VictorSchröder是的,也许我不清楚。我要说的是,在主代码中您需要能够看到更大的图片。在小助手功能中,您只需要关心该功能,就可以忽略外部发生的其他事情。
邓肯,

237

在Python中,这是一个有点宗教性的问题。即使Guido考虑从Python 3中删除它mapfilter并且reduce存在足够的反弹,最终只reduce从内置函数转移到functools.reduce

我个人认为列表理解更容易阅读。[i for i in list if i.attribute == value]由于所有行为都在表面上而不是在过滤器函数内部,因此表达式中发生的事情更加明确。

我不会太担心这两种方法之间的性能差异,因为这是微不足道的。如果确实证明这是您应用程序中的瓶颈(不太可能),我真的只会对其进行优化。

另外,由于BDFL希望filter摆脱这种语言,因此可以肯定地自动使列表理解更具Pythonic ;-)


1
感谢与Guido输入内容的链接,如果对我来说别无其他,则意味着我将不再尝试使用它们,以免我养成这种习惯,也不会支持该宗教:)
破烂不堪的

1
但是减少是使用简单工具最复杂的事情!map和filter很容易用理解取代!
njzk2 2014年

8
不知道reduce在Python3中被降级了。感谢您的见解!reduce()在像PySpark这样的分布式计算中仍然很有帮助。我认为那是一个错误
。.– Tagar

1
@Tagar您仍然可以使用reduce,您只需要从functools导入它即可
icc97

69

由于任何速度差都将是微不足道的,因此使用过滤器还是列表理解都取决于品味。总的来说,我倾向于使用理解(这里似乎与大多数其他答案一致),但是在某些情况下,我更喜欢使用filter

一个非常常见的用例是抽取某些可迭代的X的值作为谓词P(x):

[x for x in X if P(x)]

但有时您想先将某些函数应用于这些值:

[f(x) for x in X if P(f(x))]


作为一个具体的例子,考虑

primes_cubed = [x*x*x for x in range(1000) if prime(x)]

我认为这看起来比使用略好filter。但是现在考虑

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]

在这种情况下,我们要filter反对后计算值。除了两次计算多维数据集的问题(想象一个更昂贵的计算)外,还有两次写入表达式的问题,这违背了DRY的审美观。在这种情况下,我倾向于使用

prime_cubes = filter(prime, [x*x*x for x in range(1000)])

7
您是否会考虑通过其他列表理解来使用素数?例如[prime(i) for i in [x**3 for x in range(1000)]]
viki.omega9,2015年

20
x*x*x不能是素数,因为它x^2x作为一个因素,例如没有真正在数学意义上的方式,但也许它仍然helpul。(也许我们可以找到更好的东西?)
Zelphir Kaltstahl 2015年

3
请注意,如果我们不想耗尽内存,可以在最后一个示例中使用生成器表达式:prime_cubes = filter(prime, (x*x*x for x in range(1000)))
Mateen Ulhaq '16

4
@MateenUlhaq,可以对其进行优化以prime_cubes = [1]节省内存和CPU周期;-)
Dennis Krupenik,

7
@DennisKrupenik或更确切地说,[]
Mateen Ulhaq,

29

尽管filter可能是“更快的方法”,但“ Python方式”将不在乎这些事情,除非性能绝对至关重要(在这种情况下,您将不会使用Python!)。


9
对一个经常见到的论点的最新评论:在5个小时而不是10个小时内运行分析有时会有所不同,如果可以通过花一个小时优化python代码来实现,那是值得的(尤其是如果使用python而不是使用较快的语言)。
bli

但更重要的是,源代码在多大程度上减慢了我们阅读和理解它的速度!
thoni56

20

我以为我会在python 3中添加,filter()实际上是一个迭代器对象,因此您必须将filter方法调用传递给list()才能构建过滤后的列表。所以在python 2:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = filter(lambda num: num % 2 == 0, lst_a)

列表b和c具有相同的值,并且大约在相同的时间内完成,因为filter()等效[如果在z中,则x表示y中的x]。但是,在3中,相同的代码将使列表c包含过滤器对象,而不是过滤后的列表。要在3中产生相同的值:

lst_a = range(25) #arbitrary list
lst_b = [num for num in lst_a if num % 2 == 0]
lst_c = list(filter(lambda num: num %2 == 0, lst_a))

问题在于list()接受一个可迭代的参数,并从该参数创建一个新列表。结果是,在python 3中以这种方式使用filter所花费的时间是[x for x in y if z]中方法的两倍,因为您必须遍历filter()的输出以及原始列表。


13

一个重要的区别是列表理解将list在过滤器返回a 的同时返回filter,而您不能像a那样操作list(即:对其进行调用len,但不能与的返回一起使用filter)。

我自己的自学使我遇到了一些类似的问题。

话虽这么说,如果有一种方法可以list从a中获得结果filter,就像您在.NET中所做的那样lst.Where(i => i.something()).ToList(),我很想知道。

编辑:这是Python 3而不是2的情况(请参阅注释中的讨论)。


4
filter返回一个列表,我们可以在其上使用len。至少在我的Python 2.7.6中。
thiruvenkadam

7
这不是在Python 3的情况下 a = [1, 2, 3, 4, 5, 6, 7, 8] f = filter(lambda x: x % 2 == 0, a) lc = [i for i in a if i % 2 == 0] >>> type(f) <class 'filter'> >>> type(lc) <class 'list'>
Adeynack

3
“如果有一种方法可以得到结果列表...我很好奇知道这一点”。只需调用list()结果:list(filter(my_func, my_iterable))。当然,您可以将,或替换list为其他任何需要迭代的内容。但是对于除函数式程序员以外的任何人来说,使用列表理解而不是对进行显式转换的情况甚至更强。settuplefilterlist
史蒂夫·杰索普

10

我发现第二种方法更具可读性。它确切地告诉您意图是什么:过滤列表。
PS:请勿将“列表”用作变量名


7

filter如果使用内置函数,通常会稍快一些。

我希望列表理解在您的情况下会更快


python -m timeit'filter(lambda x:[1,2,3,4,5]中的x,range(10000000))'10个循环,每循环最好3:1.44秒python -m timeit'[x for x在range(10000000)中,如果x在[1,2,3,4,5]]'中有10个循环,则最好为3:每个循环860毫秒不是吗?
giaosudau 2014年

@ sepdau,lambda函数不是内置函数。列表理解能力在过去4年中有所提高-现在即使内置函数,差异也可以忽略不计
John La Rooy 2014年

7

过滤器就是这样。它过滤出列表的元素。您可以看到定义中提到的内容相同(在我之前提到的官方文档链接中)。然而,列表理解是什么,作用于后产生一个新的列表的东西前面的列表上。(两个过滤器和列表理解创造了新的名单,并取代旧的名单无法执行操作。这里一个新的名单是像一个列表(例如,一种全新的数据类型。例如将整数转换为字符串等)

在您的示例中,按照定义,使用过滤器比使用列表理解更好。但是,如果您想从列表元素中说other_attribute,在您的示例中要作为新列表进行检索,则可以使用列表理解。

return [item.other_attribute for item in my_list if item.attribute==value]

这就是我实际上记得有关过滤器和列表理解的方式。删除列表中的一些内容并保持其他元素不变,请使用过滤器。在元素上自行使用一些逻辑,并创建适合于某些目的的缩减列表,使用列表理解。


2
我很高兴知道拒绝投票的原因,以便以后不再在任何地方重复。
thiruvenkadam

过滤器和列表理解的定义不是必需的,因为它们的含义没有被争论。提出了列表理解仅应用于“新”列表,但没有主张。
阿戈斯

我用这个定义说,过滤器为您提供了具有相同元素的列表,这在某种情况下是正确的,但是通过列表理解,我们可以修改元素本身,例如将int转换为str。但是要点:-)
thiruvenkadam

4

这是我需要在列表理解进行筛选时使用的一小段内容。只是过滤器,lambda和列表(也称为猫的忠诚度和狗的清洁度)的组合。

在这种情况下,我正在读取文件,删除空白行,注释掉行,以及对行进行注释后的所有内容:

# Throw out blank lines and comments
with open('file.txt', 'r') as lines:        
    # From the inside out:
    #    [s.partition('#')[0].strip() for s in lines]... Throws out comments
    #   filter(lambda x: x!= '', [s.part... Filters out blank lines
    #  y for y in filter... Converts filter object to list
    file_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]

确实,用很少的代码就可以实现很多事情。我认为在一行中可能有点逻辑太多,以至于不容易理解,但可读性才是最重要的。
Zelphir Kaltstahl

你可以这样写 file_contents = list(filter(None, (s.partition('#')[0].strip() for s in lines)))
Steve Jessop

4

除了可接受的答案外,还有一个极端的情况,您应该使用过滤器而不是列表推导。如果列表不可散列,则无法直接使用列表推导处理它。一个真实的例子是,如果您用来pyodbc从数据库中读取结果。在fetchAll()从结果cursor是unhashable列表。在这种情况下,要直接处理返回的结果,应使用过滤器:

cursor.execute("SELECT * FROM TABLE1;")
data_from_db = cursor.fetchall()
processed_data = filter(lambda s: 'abc' in s.field1 or s.StartTime >= start_date_time, data_from_db) 

如果您在此处使用列表理解,则会出现错误:

TypeError:无法散列的类型:“列表”


1
所有列表都是不可散列的,>>> hash(list()) # TypeError: unhashable type: 'list'其次它可以正常工作:processed_data = [s for s in data_from_db if 'abc' in s.field1 or s.StartTime >= start_date_time]
Thomas Grainger

“如果列表不可散列,则无法直接使用列表理解来处理它。” 这是不正确的,而且所有列表都无法散列。
juanpa.arrivillaga

3

我花了一些时间熟悉higher order functions filterand map。因此,我习惯了它们,实际上我很喜欢filter,因为很明显它通过保留真实内容来进行过滤,而且我知道一些functional programming术语也很酷。

然后,我读了这段文章(Fluent Python书):

map和filter函数仍是Python 3中的内置函数,但是由于引入了列表理解和生成器表达式,因此它们并不那么重要。listcomp或genexp可将地图和过滤器组合在一起,但可读性更高。

现在,我想,如果您可以使用已经很广泛的成语(例如列表理解)来实现filter/ 的概念,那又何必困扰 map呢?而且mapsfilters是种功能。在这种情况下,我更喜欢使用Anonymous functionslambda。

最后,仅出于测试目的,我对这两种方法(maplistComp)都进行了计时,但没有看到任何相关的速度差异来证明对此进行论证的合理性。

from timeit import Timer

timeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))
print(timeMap.timeit(number=100))

timeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])
print(timeListComp.timeit(number=100))

#Map:                 166.95695265199174
#List Comprehension   177.97208347299602

0

奇怪的是,在Python 3上,我看到过滤器的执行速度快于列表推导。

我一直认为列表理解会更有效。类似于:[如果名称不是None,则在brand_names_db中使用名称命名]生成的字节码要好一些。

>>> def f1(seq):
...     return list(filter(None, seq))
>>> def f2(seq):
...     return [i for i in seq if i is not None]
>>> disassemble(f1.__code__)
2         0 LOAD_GLOBAL              0 (list)
          2 LOAD_GLOBAL              1 (filter)
          4 LOAD_CONST               0 (None)
          6 LOAD_FAST                0 (seq)
          8 CALL_FUNCTION            2
         10 CALL_FUNCTION            1
         12 RETURN_VALUE
>>> disassemble(f2.__code__)
2           0 LOAD_CONST               1 (<code object <listcomp> at 0x10cfcaa50, file "<stdin>", line 2>)
          2 LOAD_CONST               2 ('f2.<locals>.<listcomp>')
          4 MAKE_FUNCTION            0
          6 LOAD_FAST                0 (seq)
          8 GET_ITER
         10 CALL_FUNCTION            1
         12 RETURN_VALUE

但是它们实际上要慢一些:

   >>> timeit(stmt="f1(range(1000))", setup="from __main__ import f1,f2")
   21.177661532000116
   >>> timeit(stmt="f2(range(1000))", setup="from __main__ import f1,f2")
   42.233950221000214

8
比较无效。首先,您没有将lambda函数传递给过滤器版本,这使其默认为标识函数。当定义if not None列表理解你正在定义一个lambda功能(注意 MAKE_FUNCTION语句)。其次,结果是不同的,因为列表理解版本将仅删除None值,而过滤器版本将删除所有“虚假”值。话虽如此,微基准测试的整个目的是没有用的。那是一百万次迭代,乘以一千项!差别可以忽略不计
维克多·施罗德

-7

我拿

def filter_list(list, key, value, limit=None):
    return [i for i in list if i[key] == value][:limit]

3
i从来没有说过是dict,不需要limit。除此之外,这与OP的建议有何不同,它如何回答问题?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.