python中,for循环,map函数,list comprehension列表推导的效率比较

Posted 米意思

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于python中,for循环,map函数,list comprehension列表推导的效率比较的主要内容,如果未能解决你的问题,请参考以下文章

python|高级函数|filter|map|reduce|sorted

java Map代替List在for循环中的应用

python中的for循环取list中的第i 个值值

如何在python中实现函数式编程中的嵌套for循环?

如何在python中使用for循环进行追加工作

总结for循环及for循环增强遍历数组,list,set和map