python中,for循環,map函數,list comprehension列表推導的效率比較


在我們平時寫代碼中,肯定會遇到不少從一個列表向另一個列表進行轉化的操作,以給列表中每個int元素+1為例,通常我們會用到一下3種方式:

array = range(1000)
# 循環
a = []
for i in array:
    a.append(i+1)

#map函數
a = map(lambda x: x+1, array)

#列表推導
a = [x+1 for x in array]

究竟以上三種寫法有何差異,哪種寫法最好,之前讀google的代碼規范說推薦第三種列表推導,那么為什么推薦列表推導?

我們在ipython中用timeit進行一下簡單的評測:

#循環
array=range(1000)
a=[]
%timeit for i in array: a.append(i+1)
# 1000 loops, best of 3: 156 us per loop

#map函數
%timeit map(lambda x: x+1, array)
# 10000 loops, best of 3: 172 us per loop

#列表推導
%timeit [x+1 for x in array]
#10000 loops, best of 3: 68.7 us per loop

可以看出列表推導的優勢是非常明顯的

為什么會造成這種情況呢?我們用dis模塊查看各個方法調用了哪些底層資源

def test_for(array):
    a = []
    for i in array:
        a.append(i+1)
    return a

dis.dis(test_for)
  2           0 BUILD_LIST               0
              3 STORE_FAST               1 (a)

  3           6 SETUP_LOOP              31 (to 40)
              9 LOAD_FAST                0 (array)
             12 GET_ITER            
        >>   13 FOR_ITER                23 (to 39)
             16 STORE_FAST               2 (i)

  4          19 LOAD_FAST                1 (a)
             22 LOAD_ATTR                0 (append)
             25 LOAD_FAST                2 (i)
             28 LOAD_CONST               1 (1)
             31 BINARY_ADD          
             32 CALL_FUNCTION            1
             35 POP_TOP             
             36 JUMP_ABSOLUTE           13
        >>   39 POP_BLOCK           

  5     >>   40 LOAD_FAST                1 (a)
             43 RETURN_VALUE

可以看出for循環中,在主循環體,程序反復調用load和call

def test_map(array):
    return map(lambda x: x+1, array)

dis.dis(test_map)
  2           0 LOAD_GLOBAL              0 (map)
              3 LOAD_CONST               1 (<code object <lambda> at 0x29e4cb0, file "<ipython-input-20-4aa500644b58>", line 2>)
              6 MAKE_FUNCTION            0
              9 LOAD_FAST                0 (array)
             12 CALL_FUNCTION            2
             15 RETURN_VALUE

map循環時構造了一個匿名函數,並且用map調用了該函數call

def test_list(array):
    return [x+1 for x in array]

dis.dis(test_list):
  2           0 BUILD_LIST               0
              3 LOAD_FAST                0 (array)
              6 GET_ITER            
        >>    7 FOR_ITER                16 (to 26)
             10 STORE_FAST               1 (x)
             13 LOAD_FAST                1 (x)
             16 LOAD_CONST               1 (1)
             19 BINARY_ADD          
             20 LIST_APPEND              2
             23 JUMP_ABSOLUTE            7
        >>   26 RETURN_VALUE

列表推導居然使用了LIST_APPEND這樣一個東西去記錄結果

我們都知道調用底層的速度會更快,所以說用列表推導的方式會更快一些,因為他並沒有調用其他的函數

那么如何修改前兩種方法,使其速度更快呢?

1,for循環,我們留意到for循環中有兩個步驟,一是load,而是call,如果把load的過程記錄下來,那么速度就會更快一些

a = []
test_func = a.append
%timeit for i in array: test_func(i+1)
#10000 loops, best of 3: 100 us per loop

比較之前的寫法,有大幅度的提升

2,map函數,我們在一開始的測試中使用的是我們自定義的lambda匿名函數,如果將該匿名函數設置為底層的簡單加法,那么其速度也會有大幅提升

int_obj=1
%timeit map(int_obj.__add__, array)
#10000 loops, best of 3: 67.6 us per loop

我們驚奇的發現其速度和列表推導幾乎一樣

接下來就有一個問題:為什么有列表推導,還要有for呢?如果對於一個復雜的轉換操作,列表推導的效率其實和for是差不多的

def add(x):
    return x+1

%timeit [add(x) for x in array]
#1000 loops, best of 3: 180 us per loop

總上所述:簡單的循環映射操作,我們建議用列表推導形式,其效率更高,速度更快。復雜的循環映射操作,我們建議用for循環,這樣的代碼更加易讀易懂。而對於map方法,我們認為這是一種過時的寫法,應當少用,甚至不用。

 

參考:https://www.zhihu.com/question/34637934

以上結果實測:python 2.7.3,ipython 0.12.1


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM