列表理解与lambda +过滤器

时间:2010-06-10 10:14:00

标签: python list functional-programming filter 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)?

14 个答案:

答案 0 :(得分:503)

奇怪的是,不同的人有多少美丽。我发现列表理解比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

然后在你的主代码中(可读性非常重要)你已经用一个有希望的有意义的函数名替换了列表理解和过滤器。

答案 1 :(得分:205)

这在Python中有点宗教问题。即使 Guido considered removing map, filter and reduce from Python 3 ,也有足够的强烈反对,最终只有reduce从内置版移到了functools.reduce

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

我不会太担心两种方法之间的性能差异,因为它是边缘的。如果它被证明是你应用程序中的瓶颈,那我真的只会优化它。

此外,由于 BDFL 希望filter离开语言,然后肯定会自动使列表推导更加Pythonic; - )

答案 2 :(得分:65)

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

答案 3 :(得分:28)

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

答案 4 :(得分:15)

我以为我只是在python 3中添加它,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()相同的时间内完成[x,如果是z则为x的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中以这种方式使用过滤器所需的时间是[x for y in z]方法的两倍,因为你必须遍历filter()和原始列表的输出。

答案 5 :(得分:10)

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

我自己的自学带来了一些类似的问题。

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

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

答案 6 :(得分:9)

我发现第二种方式更具可读性。它准确地告诉您目的是什么:过滤列表 PS:不要使用'list'作为变量名称

答案 7 :(得分:7)

Filter就是这样。它过滤掉列表的元素。您可以看到定义提及相同(在我之前提到的官方文档链接中)。然而,列表推导是在对前一个列表中的某事进行操作后产生新列表的事情。(过滤器和列表推导都创建新列表而不执行操作来代替旧列表。新列表这里就像一个列表,比如一个全新的数据类型。就像将整数转换成字符串一样)

在您的示例中,根据定义,最好使用过滤器而不是列表推导。但是,如果你想要,比如列表元素中的other_attribute,你的示例中的other_attribute将被检索为新列表,那么你可以使用列表推导。

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

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

答案 8 :(得分:6)

如果使用内置函数,

通常filter稍快一些。

我希望列表理解在你的情况下稍快一点

答案 9 :(得分:5)

这是我在需要过滤列表理解时使用的短片。只是过滤器,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])]

答案 10 :(得分:5)

除了接受的答案之外,还有一个角落案例,你应该使用过滤器而不是列表理解。如果列表不可删除,则无法使用列表推导直接处理它。一个真实的例子是,如果您使用pyodbc来读取数据库中的结果。来自fetchAll()的{​​{1}}结果是不可用的列表。在这种情况下,要直接操作返回的结果,应使用过滤器:

cursor

如果您在此处使用列表推导,则会收到错误:

  

TypeError:不可用类型:' list'

答案 11 :(得分:1)

总结其他答案

翻看答案,我们看到了很多来回,列表理解或过滤器是否可能更快,或者是否更重要或pythonic关心这样的问题。最后,大多数情况下的答案是:视情况而定。

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

我的情况

就我而言,列表理解要快得多(速度是原来的两倍)。但我怀疑这取决于过滤器表达式以及使用的 Python 解释器。

自己测试

这是一个简单的代码片段,应该很容易适应。如果您对其进行概要分析(大多数 IDE 可以轻松完成),您将能够轻松决定针对您的特定情况哪个是更好的选择:

lambda

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

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))

答案 12 :(得分: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

答案 13 :(得分:-5)

我的看法

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