Python列表理解 - 希望避免重复评估

时间:2013-04-04 13:32:08

标签: python list-comprehension code-readability

我有一个列表理解,它近似于:

[f(x) for x in l if f(x)]

其中l是列表,f(x)是返回列表的昂贵函数。

我想避免在f(x)的每次非空出现时两次评估f(x)。有没有办法在列表理解中保存输出?

我可以删除最终条件,生成整个列表然后修剪它,但这似乎很浪费。

修改

建议采用两种基本方法:

内部生成器理解:

[y for y in (f(x) for x in l) if y]

或记忆。

我认为内部生成器理解对于所述问题是优雅的。事实上,我简化了问题以使其清楚,我真的想要:

[g(x, f(x)) for x in l if f(x)]

对于这种更复杂的情况,我认为memoization会产生更清晰的最终结果。

12 个答案:

答案 0 :(得分:42)

[y for y in (f(x) for x in l) if y]

会这样做。

答案 1 :(得分:11)

你应该使用memoize装饰器。这是一个有趣的link


使用链接中的记忆和“代码”:

def memoize(f):
    """ Memoization decorator for functions taking one or more arguments. """
    class memodict(dict):
        def __init__(self, f):
            self.f = f
        def __call__(self, *args):
            return self[args]
        def __missing__(self, key):
            ret = self[key] = self.f(*key)
            return ret
    return memodict(f)

@memoize
def f(x):
    # your code

[f(x) for x in l if f(x)]

答案 2 :(得分:11)

一个解决方案(如果你有重复的x值,最好)是 memoize 函数f,即创建一个包装函数来保存调用函数的参数并保存它,如果要求相同的值,则返回它。

一个非常简单的实现如下:

storage = {}
def memoized(value):
    if value not in storage:
        storage[value] = f(value)
    return storage[value]

[memoized(x) for x in l if memoized(x)]

然后在列表解析中使用此函数。这种方法在两种条件下有效,一种是理论上的,一种是实用的。第一个是函数 f 应该是确定性的,即在给定相同输入的情况下返回相同的结果,另一个是对象 x 可以用作字典键。如果第一个无效,你应该每次按定义重新计算f,而如果第二个失败则可以使用一些稍微更强大的方法。

你可以在网上找到大量的memoization实现,我认为python的新版本也包含了一些内容。

另外,从不使用小L作为变量名,这是一个坏习惯,因为它可能会与某些终端上的i或1混淆。

编辑:

作为评论,使用生成器理解(以避免创建无用的重复临时值)的可能解决方案将是这样的表达式:

[g(x, fx) for x, fx in ((x,f(x)) for x in l) if fx]

考虑到f的计算成本,原始列表中的重复次数以及处置时的内存,您需要权衡您的选择。 Memoization进行空间速度权衡,这意味着它可以保存每个结果的记录,因此如果你有大量的列表,它可能会占用内存占用成本。

答案 3 :(得分:9)

[y for y in [f(x) for x in l] if y]

对于您更新的问题,这可能很有用:

[g(x,y) for x in l for y in [f(x)] if y]

答案 4 :(得分:8)

不。没有( clean )方法来做到这一点。一个老式的循环没有错:

output = []
for x in l:
    result = f(x)
    if result: 
        output.append(result)

如果您发现难以阅读,您可以随时将其包装在一个函数中。

答案 5 :(得分:8)

如前所述,您可以使用双重理解或使用记忆。对于合理大小的问题,这是一个品味问题(并且我同意memoization看起来更干净,因为它隐藏了优化)。但是如果你正在检查一个非常大的列表,会有很大的不同:记忆将存储你计算的每一个值,并且可以快速消除你的记忆。双重理解与生成器(圆形的parens,而不是方括号)只存储您想要保留的内容。

来解决你的实际问题:

[g(x, f(x)) for x in series if f(x)]

要计算最终值,您需要xf(x)。没问题,把它们都传递给他们:

[g(x, y) for (x, y) in ( (x, f(x)) for x in series ) if y ]

再次:这应该使用生成器(圆形的parens),而不是列表推导(方括号)。否则,在开始过滤结果之前,构建整个列表。这是列表理解版本:

[g(x, y) for (x, y) in [ (x, f(x)) for x in series ] if y ] # DO NOT USE THIS

答案 6 :(得分:3)

您可以使用memoization。这是一种技术,用于通过将每个计算值的结果保存在某处来避免两次进行相同的计算。 我看到已经有一个使用memoization的答案,但我想提出一个使用python装饰器的通用实现:

def memoize(func):
    def wrapper(*args):
        if args in wrapper.d:
            return wrapper.d[args]
        ret_val = func(*args)
        wrapper.d[args] = ret_val
        return ret_val
    wrapper.d = {}
    return wrapper

@memoize
def f(x):
...

现在f是自己的备忘版本。 通过此实现,您可以使用@memoize装饰器记忆任何功能。

答案 7 :(得分:3)

关于记忆的答案很多。 Python 3标准库现在有一个lru_cache,它是 Last Recently Used Cache 。所以你可以:

from functools import lru_cache

@lru_cache()
def f(x):
    # function body here

这样你的函数只会被调用一次。您还可以指定lru_cache的大小,默认情况下为128.上面显示的memoize装饰器的问题是列表的大小可以很好地增长。

答案 8 :(得分:3)

使用map() !!

comp = [x for x in map(f, l) if x]

f是函数f(X)l是列表

map()将为列表中的每个x返回f(x)的结果。

答案 9 :(得分:1)

这是我的解决方案:

filter(None, [f(x) for x in l])

答案 10 :(得分:1)

Python 3.8开始,并引入assignment expressions (PEP 572):=运算符),可以在列表推导中使用局部变量,以避免调用同一函数两次: / p>

在我们的例子中,我们可以将f(x)的求值命名为变量y,同时使用表达式的结果来过滤列表和映射值:

[y for x in l if (y := f(x))]

答案 11 :(得分:-2)

如何定义:

def truths(L):
    """Return the elements of L that test true"""
    return [x for x in L if x]

所以,例如

> [wife.children for wife in henry8.wives]
[[Mary1], [Elizabeth1], [Edward6], [], [], []]

> truths(wife.children for wife in henry8.wives) 
[[Mary1], [Elizabeth1], [Edward6]]