如何有效地过滤Python列表推导中的计算值?
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)确实取决于外部状态,并且我们可以检测到该状态何时更改,或者我们知道列表理解期间状态不会改变,则可以在理解之前重置备注。
如果计算已经很好地捆绑到函数中,那么如何使用filter
和map
呢?
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)