Python列表推导式 vs for循环

8
为什么在Python中,列表推导式for循环有更好的性能表现? 列表推导式:
new_items = [a for a in items if a > 10]

for循环:

new_items = []
for a in items:
    if a > 10: new_items.append(a)

除了循环以外,还有哪些Python数据结构的性能较差?


3
还有许多其他例子(不涉及循环),其中一种Python结构的性能比另一种差,这是可能的吗? - 有无数个。 - Gareth Latty
@Lattyware,例如? - Tom Cruise
6
如果性能是唯一的原因,你不应太担心使用lc还是loop。相反,应该关注你整个算法是否合理,并确保你能够很好地理解自己编写的代码。 - dawg
@TomCruise filter(lambda x:x >10, items) - Ashwini Chaudhary
@Lattyware,我在面试中被问到“for循环和列表推导哪个性能更好,为什么”,所以我想我应该知道它的答案;) - Tom Cruise
显示剩余2条评论
3个回答

16

本质上,列表解析和for循环执行类似的功能,但列表解析可以省略一些开销并使代码看起来更加简洁易懂。

要理解为什么列表解析更快,你可以查看列表解析的效率,并引用其中与你问题相关的部分:

列表解析在这里执行得更好,因为你不需要从列表中加载append属性(循环程序,字节码28),然后将其作为一个函数调用(循环程序,字节码38)。相反,在列表解析中,会生成专门的LIST_APPEND字节码,以便快速地将结果添加到结果列表中(解析程序,字节码33)。

在loop_faster程序中,通过将append属性提前移出循环,并将结果放置在一个快速本地变量中(字节码9-12),可以避免属性查找的开销,从而使循环更快;但是,列表解析使用了专门的LIST_APPEND字节码,而不是产生函数调用的开销,所以它仍然胜出。

该链接还详细介绍了列表解析可能涉及的一些缺陷,建议您仔细阅读。


如果您想计时您的代码,您可以在IPython中使用“%timeit”魔术函数。 - goofd
也就是说,LC 不必担心语句之类的问题,因为 LC 中只允许表达式。 - Ashwini Chaudhary
这不太好看。首先,它是非常长的语句,所以我必须将其拆分成多行。一旦这种情况发生,我就很难理解了:[[1 if item_idx == row_idx else 0 for item_idx in range(0, 3)] for row_idx in range(0, 3)],这是来自 https://python-3-patterns-idioms-test.readthedocs.org/en/latest/Comprehensions.html 的示例。 - Schultz9999
链接似乎已经损坏 :( - masterforker
2
很久没来这里了,只是回来看看。链接确实失效了。这是从wayback机器中获取的最新URL @masterforker https://web.archive.org/web/20190319205826/http://blog.cdleary.com/2010/04/efficiency-of-list-comprehensions/ - goofd

5
假设我们在谈论CPython,你可以使用dis模块来比较生成的字节码:
>> def one():
       return [a for a in items if a > 10]

>> def two():
       res = []
       for a in items:
           if a > 10:
               res.append(a)

>> dis.dis(one)

  2           0 BUILD_LIST               0
              3 LOAD_GLOBAL              0 (items)
              6 GET_ITER
        >>    7 FOR_ITER                24 (to 34)
             10 STORE_FAST               0 (a)
             13 LOAD_FAST                0 (a)
             16 LOAD_CONST               1 (10)
             19 COMPARE_OP               4 (>)
             22 POP_JUMP_IF_FALSE        7
             25 LOAD_FAST                0 (a)
             28 LIST_APPEND              2
             31 JUMP_ABSOLUTE            7
        >>   34 RETURN_VALUE

>> dis.dis(two)
  2           0 BUILD_LIST               0
              3 STORE_FAST               0 (res)

  3           6 SETUP_LOOP              42 (to 51)
              9 LOAD_GLOBAL              0 (items)
             12 GET_ITER
        >>   13 FOR_ITER                34 (to 50)
             16 STORE_FAST               1 (a)

  4          19 LOAD_FAST                1 (a)
             22 LOAD_CONST               1 (10)
             25 COMPARE_OP               4 (>)
             28 POP_JUMP_IF_FALSE       13

  5          31 LOAD_FAST                0 (res)
             34 LOAD_ATTR                1 (append)
             37 LOAD_FAST                1 (a)
             40 CALL_FUNCTION            1
             43 POP_TOP
             44 JUMP_ABSOLUTE           13
             47 JUMP_ABSOLUTE           13
        >>   50 POP_BLOCK
        >>   51 LOAD_CONST               0 (None)
             54 RETURN_VALUE

首先,列表推导式利用了专门的LIST_APPEND操作码,而这个操作码在for循环中没有被使用。


2

来自Python维基

最常用的是for语句。它循环遍历序列中的元素,并将每个元素分配给循环变量。如果循环体简单,for循环本身的解释器开销可能会占据相当大的开销。这就是map函数的便利之处。你可以把map看作是一个移入了C代码的for。

因此,简单的for循环存在开销,而列表推导式则可以避免。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接