列表理解与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)吗?


#1楼

我拿

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

#2楼

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

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

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

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


#3楼

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

#4楼

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

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

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

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


#5楼

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

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


#6楼

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


#7楼

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


#8楼

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

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


#9楼

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

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

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

另外,由于BDFL希望filter脱离该语言,因此可以肯定地自动使列表理解更具Pythonic ;-)


#10楼

这是我需要在列表理解进行筛选时使用的一小段内容。 只是过滤器,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])]

#11楼

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


#12楼

我花了一些时间来熟悉higher order functions filtermap 。 因此,我习惯了它们,并且实际上我喜欢filter ,因为很明显它可以通过保留任何真实内容来进行过滤,而且我对知道一些functional programming术语感到很酷。

然后,我读了这段文章(Fluent Python书):

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

#13楼

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


#14楼

奇怪的是,在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

#15楼

这只是个人喜好差异。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值