列表理解与lambda +过滤器

Ago*_*gos 793 python lambda functional-programming list filter

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

我的代码看起来像这样:

my_list = [x for x in my_list if x.attribute == value]
Run Code Online (Sandbox Code Playgroud)

但后来我想,这样写它会不会更好?

my_list = filter(lambda x: x.attribute == value, my_list)
Run Code Online (Sandbox Code Playgroud)

它更具可读性,如果需要性能,可以取出lambda来获得一些东西.

问题是:使用第二种方式有什么警告吗?任何性能差异?我是否完全错过了Pythonic Way™并且应该以另一种方式(例如使用itemgetter而不是lambda)来完成它?

Dun*_*can 547

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

有两件事可能会减慢你的使用速度def.

第一个是函数调用开销:只要你使用Python函数(无论是由lambdaor 创建value),过滤器可能会比列表理解慢.几乎可以肯定这并不重要,在你对代码进行计时并发现它成为瓶颈之前,你不应该考虑性能,但差异就在那里.

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

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

def filterbyvalue(seq, value):
   for el in seq:
       if el.attribute==value: yield el
Run Code Online (Sandbox Code Playgroud)

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

  • 发电机+1.我在家里有一个链接,展示了发电机的惊人之处.您还可以通过将`[]`更改为`()`来将列表推导替换为生成器表达式.此外,我同意列表comp更漂亮. (64认同)
  • @ tnq177这是David Beasley关于发电机的介绍 - http://www.dabeaz.com/generators/ (8认同)
  • 实际上,不 - 过滤速度更快。只需使用 http://stackoverflow.com/questions/5998245/get-current-time-in-milliseconds-in-python 之类的东西运行几个快速基准测试 (2认同)
  • @skqr更好地将timeit用于基准测试,但请举例说明使用Python回调函数可以更快地找到`filter`. (2认同)
  • *"...这就是可读性真正重要的地方..."*。抱歉,但可读性 ** 始终** 很重要,即使在(罕见的)情况下,您 - 哭泣 - 不得不放弃它。 (2认同)
  • @VictorSchröder 是的,也许我不清楚。我想说的是,在主代码中,您需要能够看到更大的图景。在小助手函数中,你只需要关心那个函数,外面发生的其他事情可以忽略。 (2认同)

Ten*_*she 219

这在Python中是一个有些宗教问题.即使Guido考虑删除map,filter而且reduce从Python 3开始,还有足够的强烈反对,最终只是reduce从内置函数转移到functools.reduce.

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

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

此外,因为BDFL希望filter从语言中消失,然后肯定会自动使列表理解更加Pythonic ;-)

  • 不知道在Python3中降级是降级的.感谢您的见解!reduce()在分布式计算中仍然非常有用,比如PySpark.我认为这是一个错误.. (7认同)
  • 感谢您提供 Guido 意见的链接,如果对我来说没有别的事情,这意味着我将尝试不再使用它们,这样我就不会养成这个习惯,并且我不会支持该宗教:) (4认同)
  • 但使用简单的工具,reduce 是最复杂的!地图和过滤器很容易用理解替换! (2认同)
  • @Tagar你仍然可以使用reduce,你只需从functools导入它 (2认同)
  • +1 表示“如果事实证明它是应用程序中的瓶颈(这不太可能),我实际上只会对此进行优化。” – 这可能是题外话,但有很多不可读的代码只是因为开发人员想要保护几微秒或 20 KB 的内存。除非边际较高的内存消耗或 2 或 5 微秒确实是一个问题,否则干净的代码应始终是首选。(在这种情况下,使用“filter”与使用列表理解一样干净的代码。就我个人而言,我认为列表理解更具Python风格。) (2认同)

I. *_*edy 67

由于任何速度差异必然微乎其微,因此无论是使用过滤器还是列表理解,都归结为品味问题.一般来说,我倾向于使用理解(这似乎与大多数其他答案一致),但有一个案例我更喜欢filter.

一个非常频繁的用例是将一些可迭代X的值拉出到谓词P(x):

[x for x in X if P(x)]
Run Code Online (Sandbox Code Playgroud)

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

[f(x) for x in X if P(f(x))]
Run Code Online (Sandbox Code Playgroud)


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

primes_cubed = [x*x*x for x in range(1000) if prime(x)]
Run Code Online (Sandbox Code Playgroud)

我认为这看起来比使用稍好filter.但现在考虑一下

prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)]
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们想要filter反对后计算值.除了计算立方体两次的问题(想象一个更昂贵的计算),存在两次写表达式的问题,违反了DRY美学.在这种情况下,我会倾向于使用

prime_cubes = filter(prime, [x*x*x for x in range(1000)])
Run Code Online (Sandbox Code Playgroud)

  • `x*x*x`不能是素数,因为它有'x ^ 2`和`x`作为因子,这个例子在数学上并没有真正意义,但也许它仍然是有帮助的.(也许我们可以找到更好的东西?) (19认同)
  • 你不会考虑通过另一个列表理解使用素数吗?例如`[my(i)for i in [x**3 for x in range(1000)]] (7认同)
  • @DennisKrupenik或者更确切地说,`[]` (5认同)
  • @MateenUlhaq这可以优化为`prime_cubes = [1]`来保存内存和cpu周期;-) (4认同)
  • 请注意,如果我们不想占用内存,我们可以使用生成器表达式代替最后一个示例:`prime_cubes = filter(prime,(x*x*x for x in range(1000))) (2认同)

Uma*_*ang 28

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

  • 对一个经常看到的论点的延迟评论:有时候分析运行在5小时​​而不是10分钟是有意义的,如果可以通过花一小时优化python代码来实现,那么它是值得的(特别是如果一个是舒适的python而不是更快的语言). (9认同)
  • 基本上,Pythonic 方式是一个秘密武器,当你想说我的想法比你的更好时,你可以使用它。 (3认同)
  • 但更重要的是,源代码大大减慢了我们阅读和理解它的速度! (2认同)

Jim*_*m50 18

我以为我只是在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)
Run Code Online (Sandbox Code Playgroud)

列表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))
Run Code Online (Sandbox Code Playgroud)

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


Ade*_*ack 12

一个重要的区别是列表理解将返回一段list时间过滤器返回a filter,你不能像a一样操作list(即:调用len它,它不适用于返回filter).

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

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

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

  • 在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'>` (6认同)
  • filter返回一个列表,我们可以使用len.至少在我的Python 2.7.6中. (4认同)
  • "如果有办法得到结果清单......我很想知道它".只需在结果上调用`list()`:`list(filter(my_func,my_iterable))`.当然,您可以将`list`替换为`set`,或`tuple`,或其他任何需要迭代的东西.但对于功能程序员以外的任何人来说,使用列表理解而不是"过滤器"加上显式转换为"列表"的情况更为强烈. (2认同)

unb*_*eli 8

我发现第二种方式更具可读性.它会告诉您具体用途:过滤列表.
PS:不要使用'list'作为变量名


thi*_*dam 7

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

在您的示例中,根据定义,最好使用过滤器而不是列表推导.但是,如果您愿意,例如,从列表元素中说other_attribute,您的示例中的other_attribute将作为新列表检索,那么您可以使用list comprehension.

return [item.other_attribute for item in my_list if item.attribute==value]
Run Code Online (Sandbox Code Playgroud)

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

  • 我很高兴知道投票失败的原因,以便我将来不再重复投票. (2认同)

Joh*_*ooy 6

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

在你的情况下,我希望列表理解能够略快一些


rha*_*der 6

这是我在列表理解需要过滤某些内容时使用的短片.只是过滤器,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])]
Run Code Online (Sandbox Code Playgroud)


use*_*754 5

我花了一些时间来熟悉higher order functions filtermap。所以我习惯了它们,我真的很喜欢它们filter,因为它很明确地通过保留真实的内容来过滤,而且我知道一些术语,这让我感觉很酷functional programming

\n\n

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

\n\n
\n

映射和过滤函数仍然是 Python 3 中的内置函数,但由于引入了列表推导式和生成器 ex\xe2\x80\x90\n 表达式,它们不再那么重要。listcomp 或 genexp 完成映射和过滤器的组合工作,但更具可读性。

\n
\n\n

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

\n\n

最后,只是为了对其进行测试,我对两种方法 (maplistComp) 进行了计时,并且我没有看到任何相关的速度差异来证明对此进行争论是合理的。

\n\n
from timeit import Timer\n\ntimeMap = Timer(lambda: list(map(lambda x: x*x, range(10**7))))\nprint(timeMap.timeit(number=100))\n\ntimeListComp = Timer(lambda:[(lambda x: x*x) for x in range(10**7)])\nprint(timeListComp.timeit(number=100))\n\n#Map:                 166.95695265199174\n#List Comprehension   177.97208347299602\n
Run Code Online (Sandbox Code Playgroud)\n


小智 5

除了可接受的答案外,还有一个极端的情况,您应该使用过滤器而不是列表推导。如果列表不可散列,则无法直接使用列表推导处理它。一个真实的例子是,如果您用来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) 
Run Code Online (Sandbox Code Playgroud)

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

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

  • 所有列表都是不可散列的 `&gt;&gt;&gt; hash(list()) # TypeError: unhashable type: 'list'` 其次,这工作正常:`processed_data = [s for s in data_from_db if 'abc' in s.field1 or s.StartTime &gt;= 开始日期时间]` (2认同)
  • “如果列表是不可散列的,您就无法直接使用列表理解来处理它。” 这是不正确的,并且*所有*列表无论如何都是不可散列的。 (2认同)