列表理解与 lambda + 过滤器

my_list = [x for x in my_list if x.attribute == value]
my_list = filter(lambda x: x.attribute == value, my_list)

答案

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

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

第一个是函数调用开销:使用 Python 函数(无论是def还是lambda创建的)后,filter 的运行速度可能会比 list 理解速度慢。几乎可以肯定,这还不够重要,在对代码进行定时并发现它是瓶颈之前,您不应该对性能进行太多考虑,但是两者之间就存在差异。

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

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

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

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

这在 Python 中是一个有点宗教性的问题。即使Guido 考虑从 Python 3 中删除mapfilterreduce ,但仍然存在足够的反冲,最终只有reduce才从内置功能转移到functools.reduce

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

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

另外,由于BDFL希望filter不再使用该语言,因此可以肯定地,这会自动使列表理解更加 Pythonic ;-)

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

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

我以为我会在 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()的输出以及原始列表。

一个重要的区别是列表理解将返回一个list而过滤器返回一个filter ,您不能像list那样操作它(即:在其上调用len ,这不适用于filter的返回)。

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

话虽这么说,如果有一种方法可以从filter获取结果list ,那有点像在执行lst.Where(i => i.something()).ToList()时在. NET 中所做的lst.Where(i => i.something()).ToList()很好奇。

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

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

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

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

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

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

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

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

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

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:无法散列的类型:“列表”