簡體   English   中英

lambda 與列表理解性能

[英]lambda versus list comprehension performance

我最近使用 lambda function 發布了一個問題,並且在回復中有人提到 lambda 已經失寵,而是使用列表推導。 我對 Python 比較陌生。 我進行了一個簡單的測試:

 import time S=[x for x in range(1000000)] T=[y**2 for y in range(300)] # # time1 = time.time() N=[x for x in S for y in T if x==y] time2 = time.time() print 'time diff [x for x in S for y in T if x==y]=', time2-time1 #print N # # time1 = time.time() N=filter(lambda x:x in S,T) time2 = time.time() print 'time diff filter(lambda x:x in S,T)=', time2-time1 #print N # # #http://snipt.net/voyeg3r/python-intersect-lists/ time1 = time.time() N = [val for val in S if val in T] time2 = time.time() print 'time diff [val for val in S if val in T]=', time2-time1 #print N # # time1 = time.time() N= list(set(S) & set(T)) time2 = time.time() print 'time diff list(set(S) & set(T))=', time2-time1 #print N #the results will be unordered as compared to the other ways.:: # # time1 = time:time() N=[] for x in S. for y in T. if x==y, N append(x) time2 = time time() print 'time diff using traditional for loop' time2-time1 #print N

它們都打印相同的 N 所以我評論說 print stmt out (除了最后一種方式它是無序的),但是在重復測試中產生的時間差異很有趣,如這個例子所示:

 time diff [x for x in S for y in T if x==y]= 54.875 time diff filter(lambda x:x in S,T)= 0.391000032425 time diff [val for val in S if val in T]= 12.6089999676 time diff list(set(S) & set(T))= 0.125 time diff using traditional for loop 54.7970001698

因此,雖然我發現列表推導總體上更易於閱讀,但至少在這個示例中似乎存在一些性能問題。

所以,兩個問題:

  1. 為什么 lambda 等被推到一邊?

  2. 對於列表理解方式,是否有更有效的實現方式?如果不進行測試,您如何知道它更有效? 我的意思是,由於額外的 function 調用,lambda/map/filter 應該效率較低,但它似乎更有效率。

保羅

您的測試正在做非常不同的事情。 S 是 1M 個元素,T 是 300:

 [x for x in S for y in T if x==y]= 54.875

此選項執行 300M 相等比較。

 filter(lambda x:x in S,T)= 0.391000032425

此選項通過 S 進行 300 次線性搜索。

 [val for val in S if val in T]= 12.6089999676

此選項通過 T 進行 1M 線性搜索。

 list(set(S) & set(T))= 0.125

此選項執行兩個集合構造和一個集合交集。


這些選項之間的性能差異與每個選項使用的算法更相關,不是列表推導和lambda之間的任何差異。

當我修復您的代碼以使列表理解和對filter的調用實際上在做同樣的工作時,事情發生了很大變化:

 import time S=[x for x in range(1000000)] T=[y**2 for y in range(300)] # # time1 = time.time() N=[x for x in T if x in S] time2 = time.time() print 'time diff [x for x in T if x in S]=', time2-time1 #print N # # time1 = time.time() N=filter(lambda x:x in S,T) time2 = time.time() print 'time diff filter(lambda x:x in S,T)=', time2-time1 #print N

那么 output 更像:

 time diff [x for x in T if x in S]= 0.414485931396 time diff filter(lambda x:x in S,T)= 0.466315984726

所以列表理解的時間通常非常接近並且通常小於 lambda 表達式。

lambda 表達式被逐步淘汰的原因是,許多人認為它們的可讀性遠低於列表推導式。 我有點不情願地同意了。

問:為什么lambda等被推到一邊?

答:列表推導式和生成器表達式通常被認為是功能和可讀性的完美結合。 map()reduce()filter()與函數(通常是lambda函數)一起使用的純函數式編程風格被認為不夠清晰。 此外,Python 添加了內置函數,可以很好地處理reduce()的所有主要用途。

假設你想總結一個列表。 這里有兩種方法。

 lst = range(10) print reduce(lambda x, y: x + y, lst) print sum(lst)

注冊我作為sum()的粉絲而不是reduce()的粉絲來解決這個問題。 這是另一個類似的問題:

 lst = range(10) print reduce(lambda x, y: bool(x or y), lst) print any(lst)

any()解決方案不僅更容易理解,而且速度更快; 它具有短路評估,因此一旦找到任何真實值,它將立即停止評估。 reduce()必須遍歷整個列表。 如果列表有一百萬個項目長,並且第一個項目評估為真,那么這種性能差異將是明顯的。 順便說一句,在 Python 2.5 中添加了any() 如果您沒有,這里是舊版本 Python 的版本:

 def any(iterable): for x in iterable: if x: return True return False

假設您想從某個列表中創建一個偶數平方的列表。

 lst = range(10) print map(lambda x: x**2, filter(lambda x: x % 2 == 0, lst)) print [x**2 for x in lst if x % 2 == 0]

現在假設你想對這個正方形列表求和。

 lst = range(10) print sum(map(lambda x: x**2, filter(lambda x: x % 2 == 0, lst))) # list comprehension version of the above print sum([x**2 for x in lst if x % 2 == 0]) # generator expression version; note the lack of '[' and ']' print sum(x**2 for x in lst if x % 2 == 0)

生成器表達式實際上只是返回一個可迭代的 object。 sum()獲取可迭代對象並從中提取值,一個接一個地求和,直到所有值都被消耗完。 這是在 Python 中解決此問題的最有效方法。 相反, map()解決方案,以及在sum()調用中具有列表理解的等效解決方案,必須首先構建一個列表; 然后將此列表傳遞給sum() ,使用一次,然后丟棄。 構建列表然后再次刪除它的時間只是浪費了。 (編輯:請注意,同時具有mapfilter的版本必須構建兩個列表,一個由filter構建,一個由map兩個列表都被丟棄。) ( ) 用於基於迭代器的 map 和過濾器。但我仍然更喜歡生成器表達式解決方案,而不是任何映射/過濾器解決方案。)

通過map()filter()reduce()lambda函數組合在一起,您可以做很多強大的事情。 但是 Python 具有解決相同問題的慣用方法,這些方法同時性能更好,更易於閱讀和理解。

許多人已經指出,您正在將蘋果與橙子等進行比較,等等。但我認為沒有人展示過如何進行真正簡單的比較——列表理解與 map 加上 lambda 幾乎沒有什么可以妨礙的——而且可能:

 $ python -mtimeit -s'L=range(1000)' 'map(lambda x: x+1, L)' 1000 loops, best of 3: 328 usec per loop $ python -mtimeit -s'L=range(1000)' '[x+1 for x in L]' 10000 loops, best of 3: 129 usec per loop

在這里,您可以非常清楚地看到 lambda 的成本——大約 200 微秒,在像這樣一個足夠簡單的操作的情況下,它會淹沒操作本身。

數字當然與過濾器非常相似,因為問題不是過濾器或 map,而是 lambda 本身:

 $ python -mtimeit -s'L=range(1000)' '[x for x in L if not x%7]' 10000 loops, best of 3: 162 usec per loop $ python -mtimeit -s'L=range(1000)' 'filter(lambda x: not x%7, L)' 1000 loops, best of 3: 334 usec per loop

No doubt the fact that lambda can be less clear, or its weird connection with Sparta (Spartans had a Lambda, for "Lakedaimon", painted on their shields -- this suggests that lambda is pretty dictatorial and bloody;-) have at least as很大程度上與它逐漸過時有關,因為它的性能成本。 但后者是相當真實的。

首先,像這樣測試:

 import timeit S=[x for x in range(10000)] T=[y**2 for y in range(30)] print "v1", timeit.Timer('[x for x in S for y in T if x==y]', 'from __main__ import S,T').timeit(100) print "v2", timeit.Timer('filter(lambda x:x in S,T)', 'from __main__ import S,T').timeit(100) print "v3", timeit.Timer('[val for val in T if val in S]', 'from __main__ import S,T').timeit(100) print "v4", timeit.Timer('list(set(S) & set(T))', 'from __main__ import S,T').timeit(100)

基本上你每次測試時都在做不同的事情。 例如,當您將列表理解重寫為

[val for val in T if val in S]

性能將與“lambda/filter”構造相當。

套裝是解決這個問題的正確方法。 但是嘗試交換 S 和 T 看看需要多長時間

filter(lambda x:x in T,S) $ python -m timeit -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' 'filter(lambda x:x in S,T)' 10 loops, best of 3: 485 msec per loop $ python -m timeit -r1 -n1 -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' 'filter(lambda x:x in T,S)' 1 loops, best of 1: 19.6 sec per loop

所以你看到 S 和 T 的順序非常重要

更改列表理解的順序以匹配過濾器給出

$ python -m timeit -s'S=[x for x in range(1000000)];T=[y**2 for y in range(300)]' '[x for x in T if x in S]' 10 loops, best of 3: 441 msec per loop

所以如果事實上列表理解比我電腦上的 lambda 稍快

您的列表理解和 lambda 正在做不同的事情,與 lambda 匹配的列表理解將是[val for val in T if val in S]

效率並不是首選列表理解的原因(雖然它們實際上在幾乎所有情況下都稍微快一些)。 首選它們的原因是可讀性。

嘗試使用較小的循環體和較大的循環,例如將 T 設為一個集合,然后迭代 S。在這種情況下,在我的機器上,列表理解的速度幾乎是原來的兩倍。

你的分析做錯了。 查看timeit 模塊並重試。

lambda定義了匿名函數。 他們的主要問題是許多人不了解整個 python 庫並使用它們來重新實現已經在operatorfunctools等模塊中的功能(而且速度更快)。

列表推導與lambda 它們等效於函數式語言中的標准filtermap函數。 LC 是首選,因為它們也可以用作生成器,更不用說可讀性了。

這非常快:

 def binary_search(a, x, lo=0, hi=None): if hi is None: hi = len(a) while lo < hi: mid = (lo+hi)//2 midval = a[mid] if midval < x: lo = mid+1 elif midval > x: hi = mid else: return mid return -1 time1 = time.time() N = [x for x in T if binary_search(S, x) >= 0] time2 = time.time() print 'time diff binary search=', time2-time1

簡單地說:更少的比較,更少的時間。

如果您必須處理過濾后的結果,列表推導可以產生更大的影響。 在您的情況下,您只需構建一個列表,但如果您必須執行以下操作:

 n = [f(i) for i in S if some_condition(i)]

您將從 LC 優化中獲益:

 n = map(f, filter(some_condition(i), S))

僅僅因為后者必須構建一個中間列表(或元組,或 string,取決於 S 的性質)。 因此,您還會注意到每種方法對 memory 的影響不同,LC 將保持較低。

lambda 本身並不重要。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM