列表理解vs. lambda+过滤器

我有一个列表,我想通过项目的属性进行过滤。

以下哪项是首选(易读性,性能,其他原因)?

xs = [x for x in xs if x.attribute == value]
xs = filter(lambda x: x.attribute == value, xs)
793040 次浏览

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

我希望在你的例子中列表理解会稍微快一点

我发现第二种方式更具可读性。它确切地告诉你意图是什么:过滤列表。
注意:不要使用'list'作为变量名

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

奇怪的是,美感因人而异。我发现列表理解比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

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

这在Python中是一个有点宗教的问题。即使Guido考虑从Python 3中删除#0、#1和#2,也有足够的反弹,最终只有reduce从内置移动到functools.reduce

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

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

此外,由于BDFL希望filter从语言中消失,那么肯定会自动使列表理解更加Pythonic;-)

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

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

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

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

由于任何速度差异都注定是微乎其微的,是使用过滤器还是列表理解归结为一个品味问题。一般来说,我倾向于使用理解(这似乎同意这里的大多数其他答案),但有一种情况我更喜欢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)])

过滤器就是这样。它过滤掉列表的元素。你可以看到定义也提到了同样的内容(在我之前提到的官方文档链接中)。然而,列表理解是在前一个列表上的东西之后产生一个新列表的东西。(过滤器和列表理解都会创建新列表,而不是代替旧列表执行操作。这里的新列表类似于具有全新数据类型的列表。比如将整数转换为字符串等)

在您的示例中,根据定义,使用过滤器比列表理解更好。但是,如果您希望,例如列表元素中的other_attribute,在您的示例中要作为新列表检索,那么您可以使用列表理解。

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

这就是我如何记住过滤器和列表理解。删除列表中的一些内容并保持其他元素不变,使用过滤器。对元素使用一些逻辑,并创建一个适合某些目的的淡化列表,使用列表理解。

这是我在需要过滤列表理解时使用的一个小片段。只是过滤器、lambda和列表的组合(也称为猫的忠诚和狗的清洁)。

在这种情况下,我正在读取一个文件,删除空行、注释行以及一行注释后的任何内容:

# Throw out blank lines and commentswith 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 listfile_contents = [y for y in filter(lambda x: x != '', [s.partition('#')[0].strip() for s in lines])]

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

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

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

lst_a = range(25) #arbitrary listlst_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()的输出以及原始列表。

我花了一些时间来熟悉higher order functions#1#2。所以我习惯了它们,我实际上喜欢filter,因为它通过保持任何真实的东西来过滤,我觉得很酷,我知道一些functional programming术语。

然后我读了这段话(Fluent Python Book):

map和filter函数仍然是内置的在Python 3中,但由于引入了列表推导和生成器印象,它们并不那么重要。listcomp或genexp的工作是地图和过滤器组合,但更具可读性。

现在我想,如果你可以用已经广泛传播的习语如列表推导来实现它,为什么还要费心filter/map的概念。此外,mapsfilters是一种函数。在这种情况下,我更喜欢使用Anonymous functions lambda。

最后,为了测试它,我已经对这两种方法(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

除了接受的答案之外,还有一个角落情况,你应该使用过滤器而不是列表理解。如果列表是不可访问的,你不能直接用列表理解处理它。一个真实世界的例子是,如果你使用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)

如果你在这里使用列表理解,你会得到错误:

类型错误:不可访问类型:列表

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

我一直认为列表推导会更高性能。类似:[如果名称不是无,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            210 CALL_FUNCTION            112 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            06 LOAD_FAST                0 (seq)8 GET_ITER10 CALL_FUNCTION            112 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

总结其他答案

通过查看答案,我们已经看到了很多来回,列表理解或过滤器是否可能更快,或者关心这样的问题是否重要或Pythonic。最后,答案和大多数时候一样:这取决于。

我只是在优化代码时偶然发现了这个问题,这个问题(尽管与in表达式相结合,而不是==)非常相关-filter+lambda表达式占用了我三分之一的计算时间(几分钟)。

我的案子

在我的情况下,列表理解要快得多(速度的两倍)。但我怀疑这会因过滤器表达式和使用的Python解释器而有很大差异。

你自己试试看

这是一个简单的代码片段,应该很容易适应。如果你对它进行了分析(大多数IDE都可以轻松做到这一点),你将能够根据你的具体情况轻松决定哪个是更好的选择:

whitelist = set(range(0, 100000000, 27))
input_list = list(range(0, 100000000))
proximal_list = list(filter(lambda x: x in whitelist,input_list))
proximal_list2 = [x for x in input_list if x in whitelist]
print(len(proximal_list))print(len(proximal_list2))

如果您没有可以轻松分析的IDE,请尝试此操作(从我的代码库中提取,所以有点复杂)。此代码片段将为您创建一个配置文件,您可以使用例如snakeviz轻松可视化:

import cProfilefrom time import time

class BlockProfile:def __init__(self, profile_path):self.profile_path = profile_pathself.profiler = Noneself.start_time = None
def __enter__(self):self.profiler = cProfile.Profile()self.start_time = time()self.profiler.enable()
def __exit__(self, *args):self.profiler.disable()exec_time = int((time() - self.start_time) * 1000)self.profiler.dump_stats(self.profile_path)

whitelist = set(range(0, 100000000, 27))input_list = list(range(0, 100000000))
with BlockProfile("/path/to/create/profile/in/profile.pstat"):proximal_list = list(filter(lambda x: x in whitelist,input_list))
proximal_list2 = [x for x in input_list if x in whitelist]
print(len(proximal_list))print(len(proximal_list2))

你的问题很简单但很有趣。这只说明了python作为一种编程语言是多么灵活。一个人可以使用任何逻辑,根据自己的天赋和理解来编写程序。只要我们得到答案就行了。

在您的情况下,它只是一个简单的过滤方法,两者都可以完成,但我更喜欢第一个my_list = [x for x in my_list if x.attribute == value],因为它看起来很简单,不需要任何特殊的语法。任何人都可以理解这个命令并在需要时进行更改。(虽然第二种方法也很简单,但对于初学者来说,它仍然比第一种方法更复杂)

在性能方面,这取决于。

filter不返回列表而是迭代器,如果你需要列表“立即”过滤和列表转换,对于非常大的列表(>1M),它比列表理解慢大约40%。多达100K个元素,几乎没有区别,从600K开始就开始有差异。

如果您不转换为列表,filter实际上是瞬时的。

更多信息:https://blog.finxter.com/python-lists-filter-vs-list-comprehension-which-is-faster/

我会得出结论:使用列表理解而不是过滤器,因为它

  • 更具可读性
  • 更Pythonic
  • 更快(对于Python 3.11,请参阅附加基准,另见)

请记住,过滤器返回迭代器,而不是列表。

python3 -m timeit '[x for x in range(10000000) if x % 2 == 0]'

1个循环,最佳5:270毫秒/循环

python3 -m timeit 'list(filter(lambda x: x % 2 == 0, range(10000000)))'

1个循环,最好的5:432 msec/循环