是否可以使用列表推导添加where子句?

时间:2012-07-23 07:40:22

标签: python python-3.x list-comprehension python-assignment-expression python-3.8

考虑以下列表理解

[ (x,f(x)) for x in iterable if f(x) ]

这会根据条件f过滤迭代,并返回x,f(x)对。这种方法的问题是f(x)计算两次。 如果我们能像

那样写,那就太好了
[ (x,fx) for x in iterable if fx where fx = f(x) ]
or
[ (x,fx) for x in iterable if fx with f(x) as fx ]

但是在python中我们必须使用嵌套的理解来编写,以避免重复调用f(x)并且它使得理解看起来不那么清晰

[ (x,fx) for x,fx in ( (y,f(y) for y in iterable ) if fx ]

还有其他方法可以让它更具有pythonic和可读性吗?


更新

即将推出的python 3.8! PEP

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

4 个答案:

答案 0 :(得分:10)

您希望在python列表推导中使用let - 语句语义,其范围可用于理解的___ for..in(映射)和if ___(过滤器​​)部分,其范围取决于..for ___ in...


您的解决方案,已修改: 您的(因为您承认不可读)[ (x,fx) for x,fx in ( (y,f(y) for y in iterable ) if fx ]的解决方案是编写优化的最直接的方法。

主要思想:将x提升为元组(x,f(x))。

有些人认为最“pythonic”的做法是最初的[(x,f(x)) for x in iterable if f(x)],并接受效率低下的问题。

如果您计划执行此操作,则可以将((y,fy) for y in iterable)分解为函数。这很糟糕,因为如果您希望访问的变量多于x,fx(例如x,fx,ffx),那么您需要重写所有列表推导。因此,除非您确定只需要x,fx并计划重复使用此模式,否则这不是一个很好的解决方案。


生成器表达式:

主要思想:使用更复杂的生成器表达式替代方法:python允许你写多行。

你可以使用一个生成器表达式,python可以很好地用于:

def xfx(iterable):
    for x in iterable:
        fx = f(x)
        if fx:
            yield (x,fx)

xfx(exampleIterable)

我个人这样做。


<强>记忆化/高速缓存:

主要想法:您还可以使用(滥用?)副作用,并使f具有全局记忆缓存,因此您不会重复操作。

这可能会产生一些开销,并且需要一个策略,指出缓存应该有多大以及什么时候应该进行垃圾收集。因此,只有当你有其他用于记忆f的用途,或者如果f非常昂贵时,才应该使用它。但它会让你写...

[ (x,f(x)) for x in iterable if f(x) ]

...就像你最初想要的那样,在f两次进行昂贵的操作时没有性能,即使你在技术上称之为两次。您可以将@memoized装饰器添加到fexample(没有最大缓存大小)。只要x是可清除的(例如数字,元组,冻结集等),这将起作用。


虚拟值:

主要思想:在闭包中捕获fx = f(x)并修改列表推导的行为。

filterTrue(
    (lambda fx=f(x): (x,fx) if fx else None)() for x in iterable
)

其中filterTrue(iterable)是filter(None,iterable)。如果您的列表类型(2元组)实际上能够None,则必须修改此项。

答案 1 :(得分:10)

没有where声明,但您可以使用for“模仿”它:

a=[0]
def f(x):
    a[0] += 1
    return 2*x

print [ (x, y) for x in range(5) for y in [f(x)] if y != 2 ]
print "The function was executed %s times" % a[0]

执行:

$ python 2.py 
[(0, 0), (2, 4), (3, 6), (4, 8)]
The function was executed 5 times

如您所见,这些功能执行了5次,而不是10次或9次。

这个for构造:

for y in [f(x)]

模仿where子句。

答案 2 :(得分:5)

没有什么说你必须使用理解力。事实上,我见过的大多数样式指南要求你将它们限制为简单的结构。

您可以使用生成器表达式。

def fun(iterable):
    for x in iterable:
        y = f(x)
        if y:
            yield x, y


print list(fun(iterable))

答案 3 :(得分:3)

地图和邮编?

fnRes = map(f, iterable)
[(x,fx) for x,fx in zip(iterable, fnRes) if fx)]