如何有效地过滤Python列表中的计算值?

如何有效地过滤Python列表中的计算值?,python,list-comprehension,Python,List Comprehension,Python列表理解语法使在理解中筛选值变得容易。例如: result = [x**2 for x in mylist if type(x) is int] 将返回mylist中整数平方的列表。但是,如果测试涉及一些(昂贵的)计算,并且您希望对结果进行过滤,该怎么办?一种选择是: result = [expensive(x) for x in mylist if expensive(x)] 这将产生一个非“false”昂贵(x)值的列表,但是每个x调用两次昂贵()。有没有一种理解语法可以让你

Python列表理解语法使在理解中筛选值变得容易。例如:

result = [x**2 for x in mylist if type(x) is int]
将返回mylist中整数平方的列表。但是,如果测试涉及一些(昂贵的)计算,并且您希望对结果进行过滤,该怎么办?一种选择是:

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

这将产生一个非“false”昂贵(x)值的列表,但是每个x调用两次昂贵()。有没有一种理解语法可以让你在每x只调用一次昂贵代码的情况下进行此测试?

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

result = [y for y in (expensive(x) for x in mylist) if y]
我想这是可行的,尽管我发现嵌套理解的可读性很差

result = [x for x in map(expensive,mylist) if x]
map()将返回传递给()的mylist中每个对象的值列表。然后您可以列出并理解它,并丢弃不必要的值

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

您可以始终使用
昂贵的()
函数,因此第二次调用它只是查找
x
的计算值


.

您可以记忆昂贵的(x)(如果您经常调用昂贵的(x),您可能应该以任何方式对其进行记忆。本页提供了适用于python的memoize的实现:

这还有一个额外的好处,即昂贵(x)的运行次数可能少于N次,因为任何重复的条目都将使用上一次执行的备忘录


请注意,这假设昂贵(x)是一个真正的函数,并且不依赖于可能更改的外部状态确实取决于外部状态,您可以检测该状态何时更改,或者您知道它在列表理解过程中不会更改,然后您可以在理解之前重置备忘录。

如果计算已经很好地绑定到函数中,那么使用
过滤器和
映射如何

result = filter (None, map (expensive, mylist))
如果列表非常大,可以使用
itertools.imap

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

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

它需要更多的水平空间,但一眼就能看出它的作用,这样你就不会重复自己的工作了。

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

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
  • 这使我们完全清楚在管道的每个阶段发生了什么
  • 显式过隐式
  • 在最后一步之前,在任何地方都使用生成器,因此没有大的中间列表

  • cf:

    我会优先选择:

    itertools.ifilter(bool, (expensive(x) for x in mylist))
    
    这样做的好处是:

    • 避免使用None作为函数(将在Python 3中消除):
    • 只使用迭代器

    对于要附加到列表的
    循环,也有一个简单的
    用法:

    result = []
    for x in mylist:
        expense = expensive(x)
        if expense:
            result.append(expense)
    

    我喜欢这个解决方案。它的一个问题是map创建了一个列表,所以你会有一个潜在的大临时列表,最终会被删除。@Nick:如果创建一个大列表是一个问题,请使用
    itertools.imap
    。我认为这只有在你准备使用gen_()多次,甚至在那时,我认为过滤结果是成功的,因为您可以在一行中看到发生了什么,而不必挖掘Python 3中gen_昂贵()的定义。filter(昂贵,mylist)返回一个list:list(filter(昂贵,mylist))在可读性上达成一致。您可以在第一行指定生成器,然后在第二行执行筛选。