如何在 Python 列表理解中有效过滤计算值?

问题描述 投票:0回答:10

Python 列表理解语法可以轻松过滤理解中的值。 例如:

result = [x**2 for x in mylist if type(x) is int]

将返回 mylist 中整数平方的列表。 但是,如果测试涉及一些(成本高昂的)计算并且您想要过滤结果怎么办? 一种选择是:

result = [expensive(x) for x in mylist if expensive(x)]

这将产生一个非“假”expense(x)值的列表,但是每个x都会调用expense()两次。 是否有一种理解语法允许您在每个 x 仅调用一次昂贵的情况下进行此测试?

python list-comprehension
10个回答
26
投票

经过一分钟的思考,我得出了自己的答案。 可以通过嵌套理解来完成:

result = [y for y in (expensive(x) for x in mylist) if y]

我想这是可行的,尽管我发现嵌套理解的可读性很低


23
投票

如果计算已经很好地捆绑到函数中,那么使用

filter
map
怎么样?

result = filter (None, map (expensive, mylist))

如果列表很大,可以使用

itertools.imap


7
投票

最明显的(我认为最易读的)答案是不使用列表理解或生成器表达式,而是使用真正的生成器:

def gen_expensive(mylist):
    for item in mylist:
        result = expensive(item)
        if result:
            yield result

它需要更多的水平空间,但更容易一目了然地看到它的作用,而且你最终不会重复自己。


6
投票
result = [x for x in map(expensive,mylist) if x]

map()将返回传递给expense()的mylist中每个对象的值的列表。然后你可以列出理解它,并丢弃不必要的值。

这有点像嵌套理解,但应该更快(因为 python 解释器可以相当容易地优化它)。


5
投票

这正是生成器适合处理的问题:

result = (expensive(x) for x in mylist)
result = (do_something(x) for x in result if some_condition(x))
...
result = [x for x in result if x]  # finally, a list
  1. 这使得管道每个阶段发生的事情变得完全清晰。
  2. 显式优于隐式
  3. 在最后一步之前到处使用生成器,因此没有大型中间列表

cf:David Beazley 的“系统程序员的生成器技巧”


3
投票

在 Python 3.8 及更高版本中,赋值表达式语法(“walrus”运算符)可以完成此操作:

[e for x in mylist if (e:=expensive(x))]

2
投票

您始终可以memoize

expensive()
函数,以便第二次调用它只是查找
x
的计算值。

这只是 memoize 作为装饰器的众多实现之一


2
投票

您可以记住昂贵的(x)(如果您经常调用昂贵的(x),您可能应该以任何方式记住它。此页面提供了 python 的 memoize 实现:

http://code.activestate.com/recipes/52201/

这还有一个额外的好处,即昂贵(x)可以运行少于N次,因为任何重复的条目都将利用先前执行中的备忘录。

请注意,这假设昂贵(x)是一个真实的函数,并且不依赖于可能改变的外部状态。 如果 costy(x) 确实依赖于外部状态,并且您可以检测到该状态何时发生变化,或者您知道它在列表理解期间不会发生变化,那么您可以在理解之前重置备忘录。


1
投票

我会优先选择:

itertools.ifilter(bool, (expensive(x) for x in mylist))

这样做的好处是:


0
投票

也有

for
循环的普通旧用法来附加到列表:

result = []
for x in mylist:
    expense = expensive(x)
    if expense:
        result.append(expense)
© www.soinside.com 2019 - 2024. All rights reserved.