[英]Performance of Python list comprehension: rebuilding lambda in `if` part?
[英]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
因此,雖然我發現列表推導總體上更易於閱讀,但至少在這個示例中似乎存在一些性能問題。
所以,兩個問題:
為什么 lambda 等被推到一邊?
對於列表理解方式,是否有更有效的實現方式?如果不進行測試,您如何知道它更有效? 我的意思是,由於額外的 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()
,使用一次,然后丟棄。 構建列表然后再次刪除它的時間只是浪費了。 (編輯:請注意,同時具有map
和filter
的版本必須構建兩個列表,一個由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 庫並使用它們來重新實現已經在operator
、 functools
等模塊中的功能(而且速度更快)。
列表推導與lambda
。 它們等效於函數式語言中的標准filter
和map
函數。 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.