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

时间:2020-03-06 14:41:31  来源:igfitidea点击:

Python列表推导语法使在推导中轻松过滤值变得容易。例如:

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

将返回mylist中整数平方的列表。但是,如果测试涉及某些(昂贵的)计算并且我们想要对结果进行过滤该怎么办?一种选择是:

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

这将导致生成一个非"假"的昂贵(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解释器可以相当容易地对其进行优化)。

我们总是可以记住expensive()函数,以便第二次调用它只是对x的计算值的查找。

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

我们可能会记下昂贵的(x)(并且如果我们经常调用昂贵的(x),则可能应该以任何方式记下它。此页面提供了python的记下实现:

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

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

请注意,这假设cost(x)是一个真函数,并且不依赖于可能改变的外部状态。如果昂贵的(x)确实取决于外部状态,并且我们可以检测到该状态何时更改,或者我们知道列表理解期间状态不会改变,则可以在理解之前重置备注。

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

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:"大卫·比兹利(David Beazley)的"系统程序员的生成技巧"

我将优先选择:

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

这具有以下优势:

  • 避免使用None作为函数(在Python 3中将被取消):http://bugs.python.org/issue2186
  • 仅使用迭代器。

还有一个for循环的普通用法,即追加到列表中:

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