可以从Python列表推导中捕获返回的值以使用条件吗?

时间:2012-01-06 16:57:40

标签: python list-comprehension

我想在列表推导中构造一个值,但也要对该值进行过滤。例如:

[expensive_function(x) for x in generator where expensive_function(x) < 5]

我想避免每次迭代调用expensive_function两次。

generator可能会返回无限系列,并且列表推导不会被懒惰地评估。所以这不起作用:

[y in [expensive_function(x) for x in generator where expensive_function(x)] where y < 5]

我可以用另一种方式写这个,但是对于列表理解感觉是正确的,我确信这是一种常见的使用模式(可能与否!)。

4 个答案:

答案 0 :(得分:10)

如果generator可能无限,则您不想使用列表推导。并非所有事情都必须是单行的。

def filtered_gen(gen):
    for item in gen:
        result = expensive_function(item)
        if result < 5:
            yield result

答案 1 :(得分:2)

你应该制作2个生成器表达式:

ys_all = (expensive(x) for x in xs)
ys_filtered = (y for y in ys_all if y <5)

from itertools import imap, ifilter
ys = ifilter(lambda y : y < 5, imap(expensive, xs))

答案 2 :(得分:2)

我将回答关于如何捕获列表理解中的中间结果以用于条件的问题的部分,并忽略从无限生成器构建的列表理解的问题(显然不会发生工作),以防万一找到标题中问题答案的人来到这里。

所以,你有这样的列表理解:

[expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0]

并且您希望避免在通过过滤器时计算expensive_function两次。具有更具表现力的理解语法(Scala,Haskell等)的语言允许您简单地为从理解变量计算的表达式指定名称,这使您可以执行以下操作:

# NOT REAL PYTHON
[result for x in xrange(5) for result = expensive_function(x) if result % 2 == 0]

但您可以通过将赋值result = expensive_function(x)转换为对一个元素的序列的另一个for迭代来轻松模拟这一点:

[result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0]

证明:

>>> def expensive_function(x):
        print 'expensive_function({})'.format(x)
        return x + 10
>>> [expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0]
expensive_function(0)
expensive_function(0)
expensive_function(1)
expensive_function(2)
expensive_function(2)
expensive_function(3)
expensive_function(4)
expensive_function(4)
[10, 12, 14]
>>> [result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0]
expensive_function(0)
expensive_function(1)
expensive_function(2)
expensive_function(3)
expensive_function(4)
[10, 12, 14]

答案 3 :(得分:1)

警告这有点令人费解但完成工作。我将用一个例子来解释它。

expensive_function = math.sin

infinite generator = collections.count(0.1,0.1)

然后

[z for z in (y if y < 5 else next(iter([])) 
     for y in (math.sin(x) for x in itertools.count(0.1,0.1)))]

[0.09983341664682815,
 0.19866933079506122,
 0.2955202066613396,
 0.3894183423086505,
 0.479425538604203]

所以你的问题归结为

[z for z in (y if y < 0.5 else next(iter([])) \
         for y in (expensive_function(x) for x in generator))]

诀窍是从生成器强制StopIteration并且没有优于next(iter([]))

的优雅

此处expensive_function每次迭代仅调用一次。

使用有限生成器扩展无限生成器,具有停止条件。 由于生成器不允许raise StopIteration,我们选择了一种错综复杂的方式,即next(iter([])) 现在你有了一个有限生成器,可以在列表理解中使用

由于OP关注上述方法对非monotonic函数的应用,这里是一个虚拟的非单调函数

昂贵的非单调函数f(x) = random.randint(1,100)*x

停止条件= < 7

[z for z in (y if y < 7 else next(iter([])) for y in 
      (random.randint(1,10)*x for x in itertools.count(0.1,0.1)))]

[0.9,
 0.6000000000000001,
 1.8000000000000003,
 4.0,
 0.5,
 6.0,
 4.8999999999999995,
 3.1999999999999997,
 3.5999999999999996,
 5.999999999999999]

顺便说一下:真正意义上的sin在整个范围内都是非单调的(0,2pi)