就说我有一个清单
a = (3, 2, 9, 4)
我想给每个数字加一个并存储结果,(之后我不需要操纵结果),我的第一个想法是去:
[x + 1 for x in a]
但是关于:
tuple(x + 1 for x in a)
元组是为了更快,对吧?如果我不需要在此代码更有效之后更改结果?还有它是如何工作的,tuple
构造函数是否必须从生成器表达式中创建一个列表才能提前知道大小?提前感谢您的任何解释。
只是timeit()
:
In : a = (3, 2, 9, 4)
In : f1 = lambda: [x + 1 for x in a]
In : f2 = lambda: tuple(x + 1 for x in a)
In : timeit.timeit(f1)
Out: 0.595026969909668
In : timeit.timeit(f2)
Out: 2.360887050628662
所以看起来元组构造函数变体需要大约四倍的时间,我猜是因为列表推导已经相当优化(在 cpython 中)。
但让我们仔细看看:
In : f3 = lambda: list(x + 1 for x in a)
In : timeit.timeit(f3)
Out: 2.5421998500823975
因此这与元组构造所花费的时间大致相同,这表明性能损失在于生成器表达式的开销。(我们可以排除列表/元组构造,请参阅下面的编辑)
它甚至比列表慢两倍map()
:
In : inc = partial(operator.add,1)
In : f4 = lambda:map(inc, a)
In : timeit.timeit(f4)
Out: 1.2346529960632324
我认为这真的归结为(cpython)实现细节,所以不要依赖这个。无论如何 - 不要担心性能,它只是 2-4 的一个因素,使用最容易阅读的方法。
如果您真的遇到了性能瓶颈,请在注意到它们后进行调查和优化。我敢打赌,列表操作中的第 4 个因素将是您遇到的最少问题。
编辑: 有人提到“元组”的查找成本可能会导致减速,但事实并非如此:
In : f5 = lambda: tuple([x + 1 for x in a])
In : timeit.timeit(f5)
Out: 0.7900090217590332
所以我想这确实是生成器表达式开销减慢了速度。
该dis
模块可以让您了解代码如何在内部执行......
dis.dis(lambda a: [x + 1 for x in a])
产量...
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (a)
6 GET_ITER
>> 7 FOR_ITER 16 (to 26)
10 STORE_FAST 1 (x)
13 LOAD_FAST 1 (x)
16 LOAD_CONST 1 (1)
19 BINARY_ADD
20 LIST_APPEND 2
23 JUMP_ABSOLUTE 7
>> 26 RETURN_VALUE
...和dis.dis(lambda a: tuple(x + 1 for x in a))
产量...
1 0 LOAD_GLOBAL 0 (tuple)
3 LOAD_CONST 1 (<code object <genexpr> at 0x7f62e9eda930, file "<stdin>", line 1>)
6 MAKE_FUNCTION 0
9 LOAD_FAST 0 (a)
12 GET_ITER
13 CALL_FUNCTION 1
16 CALL_FUNCTION 1
19 RETURN_VALUE
...但是您可能无法从中推断出太多东西。如果您想知道哪个更快,请查看该timeit
模块。
在大多数情况下,tuple 和 list 之间的效率其实并没有什么太大的关系。如果你真的很在意它,你可以用它timeit
来进行测试。
tuple 和 list 最重要的区别在于 tuple 是不可变的,而 list 是可变的。这意味着您可以更改列表的值,但不能使用元组进行更改。您可以散列元组,但不能使用列表。例如
k_tuple = ('a', 'b')
k_list = ['a', 'b']
d = {}
d[k_tuple] = 'c' # It is ok
d[k_list] = 'c' #It raise exception.
更重要的是,当列表作为函数的参数时,它是通过引用分配的。当元组作为函数的参数时,它是按值分配的。