28

考虑以下列表理解

[ (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 ]

有没有其他方法可以让它更具 Python 和可读性?


更新

即将在 python 3.8 中推出!政治公众人物

# 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

4 回答 4

13

没有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子句。

于 2012-07-23T09:22:01.767 回答
11

您寻求let在 python 列表推导中具有 -statement 语义,其范围可用于推导的___ for..in(map) 和if ___(filter) 部分,其范围取决于..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装饰器添加到f:示例(没有最大缓存大小)。只要 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.

于 2012-07-23T07:54:33.113 回答
5

没有人说你必须使用理解。事实上,我见过的大多数风格指南都要求您将它们限制为简单的结构,无论如何。

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

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


print list(fun(iterable))
于 2012-07-23T07:54:22.317 回答
3

地图和邮编?

fnRes = map(f, iterable)
[(x,fx) for x,fx in zip(iterable, fnRes) if fx)]
于 2012-07-23T08:06:57.677 回答