协慌网

登录 贡献 社区

列表理解与 lambda + 过滤器

我碰巧发现自己有一个基本的过滤需求:我有一个列表,我必须通过项目的属性过滤它。

我的代码看起来像这样:

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)来完成它?

答案

很奇怪,不同的人有多少美丽。我发现列表理解比filter + lambda更清晰,但是使用你发现的更容易。但是,请停止提供已用于内置函数的变量名称,这令人困惑。 [ 最初使用的问题list作为变量名称,但是为响应此答案而更新了。 ]

有两件事可能会减慢您对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 中删除mapfilterreduce ,但是有足够的强烈反对,最终只有reduce从内置函数转移到functools.reduce

我个人认为列表推导更容易阅读。 [i for i in list if i.attribute == value]所有行为都在表面上而不是在过滤器函数内部,那么表达式[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)])