22

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 只调用一次昂贵的?

4

9 回答 9

25

经过一分钟的思考,我想出了自己的答案。它可以通过嵌套推导来完成:

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

我想这行得通,虽然我发现嵌套推导只能勉强阅读

于 2008-09-24T22:12:24.237 回答
21

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

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

itertools.imap如果列表非常大,您可以使用。

于 2008-09-24T22:23:50.137 回答
7

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

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

它需要更多的水平空间,但一眼就能看出它的作用要容易得多,而且你最终不会重复自己。

于 2008-09-24T22:24:42.163 回答
6
result = [x for x in map(expensive,mylist) if x]

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

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

于 2008-09-24T22:12:26.060 回答
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 的“系统程序员的生成器技巧”

于 2008-09-25T15:12:20.943 回答
2

您总是可以记住函数expensive(),以便第二次调用它只是查找x.

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

于 2008-09-24T22:14:18.177 回答
2

你可以记住昂贵的(x)(如果你经常调用昂贵的(x),你可能应该以任何方式记住它。这个页面给出了 python 的 memoize 实现:

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

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

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

于 2008-09-24T22:15:58.640 回答
1

我会偏爱:

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

这样做的好处是:

于 2009-05-17T01:01:11.567 回答
0

for循环附加到列表也有一个简单的旧用法:

result = []
for x in mylist:
    expense = expensive(x)
    if expense:
        result.append(expense)
于 2009-05-17T10:25:40.840 回答