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 结构的性能比另一种 Python 结构差?

4

3 回答 3

16

本质上,列表推导和 for 循环做了非常相似的事情,列表推导消除了一些开销并使其看起来很漂亮。要了解为什么这更快,您应该查看列表理解的效率并引用您的问题的相关部分:

列表推导在这里表现更好,因为您不需要从列表中加载 append 属性(循环程序,字节码 28)并将其作为函数调用(循环程序,字节码 38)。相反,在理解中,会生成一个专门的 LIST_APPEND 字节码,以便快速附加到结果列表中(理解程序,字节码 33)。

在 loop_faster 程序中,您可以通过将附加属性查找提升到循环外并将结果放入 fastlocal(字节码 9-12)中来避免附加属性查找的开销,因此它循环得更快;然而,理解使用了一个专门的 LIST_APPEND 字节码,而不是产生函数调用的开销,所以它仍然胜过。

该链接还详细介绍了与 lc 相关的一些可能的陷阱,我建议您浏览一遍。

于 2013-06-03T23:22:41.420 回答
4

假设我们在这里谈论 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 循环未使用的专用操作码。

于 2013-06-03T23:23:54.593 回答
2

来自python 维基

for 语句是最常用的。它循环遍历序列的元素,将每个元素分配给循环变量。如果循环的主体很简单,则 for 循环本身的解释器开销可能是开销的很大一部分。这就是地图功能很方便的地方。您可以将 map 视为移入 C 代码的 for。

如此简单的 for 循环有开销,以至于列表推导可以摆脱。

于 2013-06-03T23:20:47.710 回答