62

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

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

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

4

12 回答 12

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

会做。

于 2013-04-04T13:35:32.507 回答
11

一个解决方案(如果你有重复的 x 值最好)是记住函数 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 的计算成本、原始列表中的重复次数和您所使用的内存,您需要权衡您的选择。记忆化在空间速度上进行了权衡,这意味着它会跟踪保存它的每个结果,因此如果您有大量列表,则在内存占用方面可能会变得昂贵。

于 2013-04-04T13:40:01.963 回答
10

您应该使用 memoize 装饰器。这是一个有趣的链接


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

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)]
于 2013-04-04T13:38:33.800 回答
10

开始Python 3.8,并引入赋值表达式(PEP 572):=运算符),可以在列表推导中使用局部变量以避免调用两次相同的函数:

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

[y for x in l if (y := f(x))]
于 2019-04-27T15:26:54.400 回答
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]
于 2013-04-04T13:34:54.367 回答
8

没有。没有(干净的)方法可以做到这一点。老式循环没有任何问题:

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

如果你觉得这很难阅读,你总是可以将它包装在一个函数中。

于 2013-04-04T13:34:02.050 回答
8

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

要解决您的实际问题:

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

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

[g(x, y) for (x, y) in [ (x, f(x)) for x in series ] if y ] # DO NOT USE THIS
于 2013-04-04T14:36:48.217 回答
4

关于记忆有很多答案。Python 3 标准库现在有一个lru_cache,它是最近使用的缓存。这样你就可以:

from functools import lru_cache

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

这样你的函数只会被调用一次。您还可以指定 的大小lru_cache,默认为 128。上面显示的 memoize 装饰器的问题是列表的大小可能会变得无法控制。

于 2014-09-10T03:56:45.073 回答
3

您可以使用记忆。这是一种技术,用于通过将每个计算值的结果保存在某处来避免进行两次相同的计算。我看到已经有一个使用 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通过这个实现,您可以使用装饰器来记忆任何功能。

于 2013-04-04T13:44:40.337 回答
3

map()!!

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

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

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

于 2017-09-15T19:26:33.717 回答
1

这是我的解决方案:

filter(None, [f(x) for x in l])
于 2013-04-04T14:17:01.427 回答
-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]]
于 2013-04-04T19:22:31.327 回答