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)]
要计算最终的值,你需要x
和f(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]]