Python中的列表推导:高效选择列表中的元素

9 投票
7 回答
8742 浏览
提问于 2025-04-15 13:21

假设我有一个元素列表,我想根据某个特定的函数(比如与另一个元素的距离)来选择其中的一些元素。

我希望得到一个包含距离和元素的元组列表。所以,我写了以下代码:

result = [ ( myFunction(C), C) for C in originalList if myFunction(C) < limit ]

但是,myFunction是一个非常耗时的函数,而originalList又相当大。所以这样做的话,myFunction会对每个被选中的元素调用两次。

那么,有没有办法避免这种情况呢?

我还有另外两种选择,但效果都不是很好:

  1. 第一种是先创建一个未过滤的列表:

    unfiltered = [ (myFunction(C),C) for C in originalList ]
    

    然后再对它进行排序:

    result = [ (dist,C) for dist,C in unfiltered if dist < limit ]
    

    但这样的话,我就会复制我的originalList,浪费一些内存(因为列表可能很大——超过10,000个元素)。

  2. 第二种方法比较复杂,不太符合Python的风格,但效率高(这是我们能做的最好方法,因为函数应该对每个元素只计算一次)。myFunction会把它上一次的结果存储在一个全局变量中(比如叫lastResult),然后在列表推导中重用这个值。

    result = [ (lastResult,C) for C in originalList if myFunction(C) < limit ]
    

你有没有更好的主意,能以高效且符合Python风格的方式实现这个呢?

谢谢你的回答。

7 个回答

3

先计算好距离,然后再筛选出结果:

with_distances = ((myFunction(C), C) for C in originalList)
result = [C for C in with_distances if C[0] < limit]

注意:我不是创建一个新的列表,而是用生成器表达式来构建距离和元素的配对。

4

别用列表推导式,这里用普通的for循环就可以了。

9

当然,下面这两者之间的区别是:

[f(x) for x in list]

而这个:

(f(x) for x in list)

第一个会把列表直接生成在内存里,而第二个是一个新的生成器,它会延迟计算。

所以,简单来说,把“未过滤”的列表写成一个生成器就可以了。这里是你的代码,生成器直接写在里面:

def myFunction(x):
    print("called for: " + str(x))
    return x * x

originalList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
limit = 10
result =   [C2 for C2 in ((myFunction(C), C) for C in originalList) if C2[0] < limit]
# result = [C2 for C2 in [(myFunction(C), C) for C in originalList] if C2[0] < limit]

注意,你在打印输出时不会看到这两者有什么区别,但如果你查看内存使用情况,注释掉的第二个语句会占用更多的内存。

要对你问题中的代码做一个简单的修改,把未过滤的部分改成这样:

unfiltered = [ (myFunction(C),C) for C in originalList ]
             ^                                         ^
             +---------- change these to (..) ---------+
                                 |
                                 v
unfiltered = ( (myFunction(C),C) for C in originalList )

撰写回答