[英]Python list comprehension - want to avoid repeated evaluation
我有一個近似於的列表理解:
[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)]
對於這種更復雜的情況,我認為記憶會產生更清晰的最終結果。
[y for y in (f(x) for x in l) if y]
會做。
一個解決方案(如果你有重復的 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 的計算成本、原始列表中的重復數量和您處置的內存,您需要權衡您的選擇。 記憶進行空間速度權衡,這意味着它會跟蹤保存它的每個結果,因此如果您有大量列表,則在內存占用方面可能會變得昂貴。
您應該使用 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]
從Python 3.8
開始,並引入賦值表達式 (PEP 572) ( :=
運算符),可以在列表推導式中使用局部變量以避免兩次調用相同的函數:
在我們的例子中,我們可以將f(x)
命名為變量y
同時使用表達式的結果來過濾列表以及映射值:
[y for x in l if (y := f(x))]
不。 沒有(干凈的)方法可以做到這一點。 一個老式的循環沒有任何問題:
output = []
for x in l:
result = f(x)
if result:
output.append(result)
如果你覺得這很難閱讀,你總是可以把它包裝在一個函數中。
正如前面的答案所示,您可以使用雙重理解或使用記憶。 對於大小合理的問題,這是一個品味問題(我同意記憶看起來更干凈,因為它隱藏了優化)。 但是如果你正在檢查一個非常大的列表,就會有一個巨大的不同:記憶化將存儲你計算的每一個值,並且可以迅速耗盡你的記憶。 使用生成器(圓括號,而不是方括號)的雙重理解僅存儲您想要保留的內容。
來解決您的實際問題:
[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 ]
再次:這應該使用生成器(圓括號),而不是列表理解(方括號)。 否則,您將在開始過濾結果之前構建整個列表。 這是列表理解版本:
[g(x, y) for (x, y) in [ (x, f(x)) for x in series ] if y ] # DO NOT USE THIS
已經有很多關於記憶的答案。 Python 3 標准庫現在有一個lru_cache
,它是一個最近使用的緩存。 這樣你就可以:
from functools import lru_cache
@lru_cache()
def f(x):
# function body here
這樣你的函數只會被調用一次。 您還可以指定lru_cache
的大小,默認情況下為 128。上面顯示的 memoize 裝飾器的問題是列表的大小可能會變得無法控制。
您可以使用記憶功能。 這是一種通過將每個計算值的結果保存在某處來避免進行兩次相同計算的技術。 我看到已經有一個使用 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
任何函數。
使用map()
!
comp = [x for x in map(f, l) if x]
f
是函數f(X)
, l
是列表
map()
將為列表中的每個 x 返回f(x)
的結果。
這是我的解決方案:
filter(None, [f(x) for x in l])
如何定義:
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]]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.