有人写道:“Python很聪明。只要您正在迭代的对象具有__len__
或__length_hint__
方法,Python将调用它来确定大小并预分配数组。”
据我所知,在列表推导式中没有预分配。Python无法从输入的大小中得知输出的大小。
看看这个Python 2.6代码:
>>> def foo(func, iterable):
... return [func(i) for i in iterable]
...
>>> import dis
2 0 BUILD_LIST 0 #### build empty list
3 DUP_TOP
4 STORE_FAST 2 (_[1])
7 LOAD_FAST 1 (iterable)
10 GET_ITER
>> 11 FOR_ITER 19 (to 33)
14 STORE_FAST 3 (i)
17 LOAD_FAST 2 (_[1])
20 LOAD_FAST 0 (func)
23 LOAD_FAST 3 (i)
26 CALL_FUNCTION 1
29 LIST_APPEND #### stack[-2].append(stack[-1])
30 JUMP_ABSOLUTE 11
>> 33 DELETE_FAST 2 (_[1])
36 RETURN_VALUE
它只是建立一个空列表,并附加迭代器提供的任何内容。
现在看看这段代码,其中列表推导式中有一个'if'语句:
>>> def bar(func, iterable):
... return [func(i) for i in iterable if i]
...
>>> import dis
2 0 BUILD_LIST 0
3 DUP_TOP
4 STORE_FAST 2 (_[1])
7 LOAD_FAST 1 (iterable)
10 GET_ITER
>> 11 FOR_ITER 30 (to 44)
14 STORE_FAST 3 (i)
17 LOAD_FAST 3 (i)
20 JUMP_IF_FALSE 17 (to 40)
23 POP_TOP
24 LOAD_FAST 2 (_[1])
27 LOAD_FAST 0 (func)
30 LOAD_FAST 3 (i)
33 CALL_FUNCTION 1
36 LIST_APPEND
37 JUMP_ABSOLUTE 11
>> 40 POP_TOP
41 JUMP_ABSOLUTE 11
>> 44 DELETE_FAST 2 (_[1])
47 RETURN_VALUE
>>>
相同的代码,加上一些避免LIST_APPEND的代码。
在Python 3.X中,您需要深入挖掘:
>>> import dis
>>> def comprehension(f, iterable): return [f(i) for i in iterable]
...
>>> dis.dis(comprehension)
1 0 LOAD_CLOSURE 0 (f)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object <listcomp> at 0x00C4B8D
8, file "<stdin>", line 1>)
9 MAKE_CLOSURE 0
12 LOAD_FAST 1 (iterable)
15 GET_ITER
16 CALL_FUNCTION 1
19 RETURN_VALUE
>>> dis.dis(comprehension.__code__.co_consts[1])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 18 (to 27)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (f)
15 LOAD_FAST 1 (i)
18 CALL_FUNCTION 1
21 LIST_APPEND 2
24 JUMP_ABSOLUTE 6
>> 27 RETURN_VALUE
>>>
这是老生常谈的技巧:先建立一个空列表,然后迭代可迭代对象,在必要时将元素添加到列表中。我在这里看不到任何预分配。
你想到的优化方法是在单个操作码内使用,例如list.extend(iterable)
的实现可以在iterable
能够准确报告其长度时进行预分配。list.append(object)
只接受单个对象而不是可迭代对象。
http://svn.python.org/view/python/branches/py3k/Objects/listobject.c?view=markup
在页面中按Ctrl-F搜索_PyObject_LengthHint
。 - John Machin