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

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

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

对于这种更复杂的情况,我认为备忘录产生了一个更清洁的最终结果。

一个解决scheme(最好如果你有重复的价值x)将记忆函数f,即创build一个包装函数,保存调用该函数的参数,并保存它,比返回它,如果相同的值。

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

 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应该是确定性的,即给定相同的input返回相同的结果,另一个是对象x可以用作字典键。 如果第一个是无效的,那么每次定义都要重新计算f,而如果第二个失败了,可以使用一些稍微强健的方法。

你可以在网上find很多memoization的实现,我认为python的新版本也包含了一些东西。

在附注中,不要使用小L作为variables名,这是一个坏习惯,因为它可能与某个terminal上的i或1混淆。

编辑:

作为评论,一个可能的解决scheme使用生成器理解(避免创build无用的重复临时)将是这样的expression式:

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

考虑到f的计算成本,原始列表中的重复次数以及处置时的内存,您需要权衡您的select。 记忆是一个空间 – 速度的折衷,这意味着它保持跟踪每个结果,保存它,所以如果你有大量的列表,它可以变成昂贵的内存占领。

[y for y in (f(x) for x in l) if y]将会做

你应该使用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)] 
 [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] 

正如前面的答案所显示的,你可以使用双重理解或使用记忆。 对于合理大小的问题,这是一个品味的问题(我同意memoization看起来更清洁,因为它隐藏了优化)。 但是,如果您正在查看一个非常大的列表, 则会有很大的差异: 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 ] 

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

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

不。 没有( 干净 )的方式来做到这一点。 老式的循环没有什么不妥:

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

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

你可以使用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装饰器来@memoize任何函数。

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

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

这样你的function只能被调用一次。 您还可以指定lru_cache的大小,默认情况下这是128.上面显示的memoize装饰器的问题是,列表的大小可以非常容易地增长。

这是我的解决scheme:

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

使用map()

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

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

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

如何定义:

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