我们可以通过使用dis 模块反汇编每个代码示例来更深入地了解发生了什么。
标准追加:
dis.dis("""
list_of_interest = []
for i in range(1000):
list_of_interest.append(i)
""")
给我们
2 0 BUILD_LIST 0
2 STORE_NAME 0 (list_of_interest)
3 4 SETUP_LOOP 26 (to 32)
6 LOAD_NAME 1 (range)
8 LOAD_CONST 0 (1000)
10 CALL_FUNCTION 1
12 GET_ITER
>> 14 FOR_ITER 14 (to 30)
16 STORE_NAME 2 (i)
4 18 LOAD_NAME 0 (list_of_interest)
20 LOAD_METHOD 3 (append)
22 LOAD_NAME 2 (i)
24 CALL_METHOD 1
26 POP_TOP
28 JUMP_ABSOLUTE 14
>> 30 POP_BLOCK
>> 32 LOAD_CONST 1 (None)
34 RETURN_VALUE
仔细观察,我们看到在循环中调用 append 方法(就像我们写的那样)。深入研究实现(here),我们看到这最终调用了app1,后者调用了list_resize,它过度分配以尝试减少重新分配。
另外两个例子:
正如其他答案中所述,range 实际上确实实现了__len__ 制作
list_of_interest = list(range(1000))
和
b = tuple(range(1000))
list_of_interest = list(b)
工作方式非常相似。
拆解第一个给出:
1 0 LOAD_NAME 0 (list)
2 LOAD_NAME 1 (range)
4 LOAD_CONST 0 (1000)
6 CALL_FUNCTION 1
8 CALL_FUNCTION 1
10 STORE_NAME 2 (list_of_interest)
12 LOAD_CONST 1 (None)
14 RETURN_VALUE
第二个:
2 0 LOAD_NAME 0 (tuple)
2 LOAD_NAME 1 (range)
4 LOAD_CONST 0 (1000)
6 CALL_FUNCTION 1
8 CALL_FUNCTION 1
10 STORE_NAME 2 (b)
3 12 LOAD_NAME 3 (list)
14 LOAD_NAME 2 (b)
16 CALL_FUNCTION 1
18 STORE_NAME 4 (list_of_interest)
20 LOAD_CONST 1 (None)
22 RETURN_VALUE
在这两种情况下,我们都调用定义为here 的list 构造函数。我们看到,如果提供给list 的可迭代对象有一个长度,那么我们调用list_preallocate_exact,它会按照您的预期执行。否则我们在一个空列表上调用list_extend。如果我们的迭代器不是列表或元组(在这种情况下我们知道它不是),我们将调用PyObject_LengthHint 来猜测迭代器的长度并以此方式预分配空间。由于长度提示可能太大或太小,我们可能需要在以标准方式循环遍历迭代器时分配更多内存,并且在提示太大的情况下,我们可能会在完成循环后释放空间。
其他情况:
我们可以尝试其他示例,但我们会看到上面的 list 构造函数有几个特殊情况。通常我们接下来会去list_extend,它也有几个特殊情况。如果我们的迭代器没有实现长度或长度提示,we default to a length hint of 8。为什么是8?你得问维护者那个。
结论:
如果 Python 从一开始就可以知道结果列表所需的长度,那么您将只有一个分配。如果它必须猜测,您可能有不止一个或一个释放。如果在循环中追加或提供没有长度或长度提示的迭代器的情况下无法猜测,您将有多个分配。