为什么列表解析比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 结构差?
为什么列表解析比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 结构差?
本质上,列表推导和 for 循环做了非常相似的事情,列表推导消除了一些开销并使其看起来很漂亮。要了解为什么这更快,您应该查看列表理解的效率并引用您的问题的相关部分:
列表推导在这里表现更好,因为您不需要从列表中加载 append 属性(循环程序,字节码 28)并将其作为函数调用(循环程序,字节码 38)。相反,在理解中,会生成一个专门的 LIST_APPEND 字节码,以便快速附加到结果列表中(理解程序,字节码 33)。
在 loop_faster 程序中,您可以通过将附加属性查找提升到循环外并将结果放入 fastlocal(字节码 9-12)中来避免附加属性查找的开销,因此它循环得更快;然而,理解使用了一个专门的 LIST_APPEND 字节码,而不是产生函数调用的开销,所以它仍然胜过。
该链接还详细介绍了与 lc 相关的一些可能的陷阱,我建议您浏览一遍。
假设我们在这里谈论 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 循环未使用的专用操作码。
for 语句是最常用的。它循环遍历序列的元素,将每个元素分配给循环变量。如果循环的主体很简单,则 for 循环本身的解释器开销可能是开销的很大一部分。这就是地图功能很方便的地方。您可以将 map 视为移入 C 代码的 for。
如此简单的 for 循环有开销,以至于列表推导可以摆脱。