list comprehension vs. lambda + filter
我碰巧发现自己有一个基本的过滤需求:我有一个列表,我必须按项目的属性过滤它。
我的代码如下:
1 | my_list = [x for x in my_list if x.attribute == value] |
但后来我想,这样写不是更好吗?
1 | my_list = filter(lambda x: x.attribute == value, my_list) |
它的可读性更强,如果性能需要,lambda可以被取出以获得一些东西。
问题是:使用第二种方法有什么注意事项吗?性能有什么不同吗?我是不是错过了Python之路?完全地,应该用另一种方式来做(例如使用itemgetter而不是lambda)?
奇怪的是,对于不同的人来说,美的差异有多大。我发现清单的理解比
有两件事可能会减慢你使用
第一个是函数调用开销:一旦使用了python函数(无论是由
另一个可能应用的开销是lambda被强制访问一个作用域变量(
另一个要考虑的选择是使用生成器而不是列表理解:
1 2 3 | def filterbyvalue(seq, value): for el in seq: if el.attribute==value: yield el |
然后,在您的主代码(可读性真正重要的地方)中,您已经用一个希望有意义的函数名替换了列表理解和过滤器。
这在Python身上是一个宗教问题。尽管guido考虑从python 3中删除
就我个人而言,我觉得列表理解更容易阅读。从表达式
我不会太担心这两种方法之间的性能差异,因为它是边际的。如果它被证明是您的应用程序中的瓶颈(这不太可能),那么我真的只会对此进行优化。
而且,既然bdfl希望
因为任何速度差都是很小的,所以不管是使用过滤器还是列表理解,归根结底都取决于品味。一般来说,我倾向于使用理解(这似乎与这里的大多数其他答案一致),但有一种情况我更喜欢使用
一个非常常见的用例是根据谓词p(x)提取一些iterable x的值:
1 | [x for x in X if P(x)] |
但有时您希望首先对值应用一些函数:
1 | [f(x) for x in X if P(f(x))] |
作为一个具体的例子,考虑
1 | primes_cubed = [x*x*x for x in range(1000) if prime(x)] |
我认为这比使用
1 | prime_cubes = [x*x*x for x in range(1000) if prime(x*x*x)] |
在这种情况下,我们希望对后计算值执行
1 | prime_cubes = filter(prime, [x*x*x for x in range(1000)]) |
虽然
我想在python 3中,filter()实际上是一个迭代器对象,所以您必须将filter方法调用传递到list()才能构建筛选列表。所以在Python 2中:
1 2 3 | 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。但是,在3中,相同的代码将使列表C包含一个筛选对象,而不是一个筛选列表。要在3中生成相同的值:
1 2 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()将iterable作为其参数,并从该参数创建一个新的列表。结果是,在python 3中以这种方式使用filter,所花费的时间是[x for x in y if z]方法的两倍,因为您必须迭代filter()的输出以及原始列表。
一个重要的区别是,列表理解将返回一个
我自己的自学使我遇到了类似的问题。
也就是说,如果有一种方法可以从一个
编辑:这是Python3的情况,而不是2(参见注释中的讨论)。
我发现第二种方式更具可读性。它确切地告诉你目的是什么:过滤列表。ps:不要将"list"用作变量名
过滤就是这样。它过滤掉列表中的元素。您可以看到定义提到相同的内容(在我前面提到的官方文档链接中)。然而,列表理解是指在对上一个列表的某个内容执行操作之后生成新列表的内容。(筛选和列表理解都会创建新列表,而不是代替旧列表执行操作)。这里的一个新列表类似于一个具有全新数据类型的列表。例如将整数转换为字符串等)
在您的示例中,根据定义,使用过滤比列表理解更好。但是,如果您愿意,比如说列表元素中的其他_属性,在您的示例中是作为一个新列表来检索的,那么您可以使用列表理解。
1 | return [item.other_attribute for item in my_list if item.attribute==value] |
这就是我对过滤和列表理解的实际记忆。删除列表中的一些内容并保持其他元素的完整性,使用filter。在元素中单独使用一些逻辑,并创建一个适合某些目的的减淡列表,使用列表理解。
一般来说,如果使用内置函数,
在你的情况下,我希望单子理解得稍微快一点
除了接受的答案之外,还有一个角落案例,当您应该使用过滤器而不是列表理解时。如果列表不可显示,则不能直接用列表理解来处理它。现实世界中的一个例子是,如果使用
1 2 3 | 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: unhashable type: 'list'
下面是一个简短的片段,当我需要在列表理解后过滤一些内容时我会用到。只是过滤器、lambda和列表的组合(也称为猫的忠诚度和狗的清洁度)。
在本例中,我正在读取一个文件,去掉空白行、注释行以及行上注释后的任何内容:
1 2 3 4 5 6 7 | # 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])] |
我花了一些时间熟悉了
然后我读了这段话(流利的python书):
The map and filter functions are still builtins
in Python 3, but since the introduction of list comprehensions and generator ex‐
pressions, they are not as important. A listcomp or a genexp does the job of map and
filter combined, but is more readable.
现在我想,如果你能用已经广泛传播的习语如列表理解来实现它,为什么还要为
最后,为了测试它,我对两种方法(
1 2 3 4 5 6 7 8 9 10 | 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 |
奇怪的是,在python3上,我看到过滤器比列表理解执行得更快。
我一直认为清单的理解会更有效。类似:[品牌中名称的名称如果名称不是"无",则为"数据库"]生成的字节码要好一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | >>> 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 |
但实际上速度较慢:
1 2 3 4 | >>> 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 |
我的拿来
1 2 | def filter_list(list, key, value, limit=None): return [i for i in list if i[key] == value][:limit] |