假设我有一个列表
a = (3, 2, 9, 4)
如果我想给每个数字加上1并存储结果(之后不需要对结果进行操作),我的第一个想法是这样的:
[x + 1 for x in a]
但是这里还有一个问题:
tuple(x + 1 for x in a)
元组速度更快,对吗?如果我在之后不需要更改结果,那么这段代码是否更加高效呢?实际上,它是如何工作的?tuple
构造函数是否必须将生成器表达式转换为列表以预先知道大小?感谢任何解释。
假设我有一个列表
a = (3, 2, 9, 4)
如果我想给每个数字加上1并存储结果(之后不需要对结果进行操作),我的第一个想法是这样的:
[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
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
模块。
timeit
进行测试。
元组和列表最重要的区别是元组是不可变的,而列表是可变的。这意味着您可以更改列表的值,但无法更改元组的值。您可以对元组进行哈希,但不能对列表进行哈希。例如:k_tuple = ('a', 'b')
k_list = ['a', 'b']
d = {}
d[k_tuple] = 'c' # It is ok
d[k_list] = 'c' #It raise exception.