在实例化和检索元素时,元组和列表之间是否存在性能差异?
通常,您可能希望元组稍快一些.但是你绝对应该测试你的特定情况(如果差异可能会影响你的程序的性能 - 记住"过早优化是所有邪恶的根源").
Python使这很容易:timeit是你的朋友.
$ python -m timeit "x=(1,2,3,4,5,6,7,8)" 10000000 loops, best of 3: 0.0388 usec per loop $ python -m timeit "x=[1,2,3,4,5,6,7,8]" 1000000 loops, best of 3: 0.363 usec per loop
和...
$ python -m timeit -s "x=(1,2,3,4,5,6,7,8)" "y=x[3]" 10000000 loops, best of 3: 0.0938 usec per loop $ python -m timeit -s "x=[1,2,3,4,5,6,7,8]" "y=x[3]" 10000000 loops, best of 3: 0.0649 usec per loop
所以在这种情况下,元组的实例化速度几乎要快一个数量级,但是对于列表,项目访问实际上要快一些!因此,如果您创建了几个元组并多次访问它们,那么使用列表实际上可能会更快.
当然,如果你想改变一个项目,列表肯定会更快,因为你需要创建一个全新的元组来改变它的一个项目(因为元组是不可变的).
元组往往比几乎每个类别中的列表表现更好:
1)元组可以不断折叠.
2)可以重用元组而不是复制元组.
3)元组是紧凑的,不会过度分配.
4)元组直接引用它们的元素.
常量元组可以通过Python的窥孔优化器或AST优化器进行预先计算.另一方面,列表从头开始构建:
>>> from dis import dis >>> dis(compile("(10, 'abc')", '', 'eval')) 1 0 LOAD_CONST 2 ((10, 'abc')) 3 RETURN_VALUE >>> dis(compile("[10, 'abc']", '', 'eval')) 1 0 LOAD_CONST 0 (10) 3 LOAD_CONST 1 ('abc') 6 BUILD_LIST 2 9 RETURN_VALUE
运行tuple(some_tuple)
立即返回.由于元组是不可变的,因此不必复制它们:
>>> a = (10, 20, 30) >>> b = tuple(a) >>> a is b True
相反,list(some_list)
要求将所有数据复制到新列表:
>>> a = [10, 20, 30] >>> b = list(a) >>> a is b False
由于元组的大小是固定的,因此它可以比需要过度分配以使append()操作有效的列表更紧凑地存储.
这为元组提供了一个很好的空间优势:
>>> import sys >>> sys.getsizeof(tuple(iter(range(10)))) 128 >>> sys.getsizeof(list(iter(range(10)))) 200
以下是Objects/listobject.c中的注释,它解释了列表正在执行的操作:
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
* Note: new_allocated won't overflow because the largest possible value
* is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
*/
对象的引用直接包含在元组对象中.相比之下,列表有一个额外的间接层指向外部指针数组.
这为元组提供了索引查找和解包的小速度优势:
$ python3.6 -m timeit -s 'a = (10, 20, 30)' 'a[1]' 10000000 loops, best of 3: 0.0304 usec per loop $ python3.6 -m timeit -s 'a = [10, 20, 30]' 'a[1]' 10000000 loops, best of 3: 0.0309 usec per loop $ python3.6 -m timeit -s 'a = (10, 20, 30)' 'x, y, z = a' 10000000 loops, best of 3: 0.0249 usec per loop $ python3.6 -m timeit -s 'a = [10, 20, 30]' 'x, y, z = a' 10000000 loops, best of 3: 0.0251 usec per loop
以下是元组(10, 20)
的存储方式:
typedef struct {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
Py_ssize_t ob_size;
PyObject *ob_item[2]; /* store a pointer to 10 and a pointer to 20 */
} PyTupleObject;
以下是列表[10, 20]
的存储方式:
PyObject arr[2]; /* store a pointer to 10 and a pointer to 20 */
typedef struct {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
Py_ssize_t ob_size;
PyObject **ob_item = arr; /* store a pointer to the two-pointer array */
Py_ssize_t allocated;
} PyListObject;
请注意,元组对象直接包含两个数据指针,而列表对象有一个额外的间接层,用于保存两个数据指针的外部数组.
该dis
模块反汇编函数的字节代码,有助于查看元组和列表之间的区别.
在这种情况下,您可以看到访问元素会生成相同的代码,但分配元组比分配列表要快得多.
>>> def a(): ... x=[1,2,3,4,5] ... y=x[2] ... >>> def b(): ... x=(1,2,3,4,5) ... y=x[2] ... >>> import dis >>> dis.dis(a) 2 0 LOAD_CONST 1 (1) 3 LOAD_CONST 2 (2) 6 LOAD_CONST 3 (3) 9 LOAD_CONST 4 (4) 12 LOAD_CONST 5 (5) 15 BUILD_LIST 5 18 STORE_FAST 0 (x) 3 21 LOAD_FAST 0 (x) 24 LOAD_CONST 2 (2) 27 BINARY_SUBSCR 28 STORE_FAST 1 (y) 31 LOAD_CONST 0 (None) 34 RETURN_VALUE >>> dis.dis(b) 2 0 LOAD_CONST 6 ((1, 2, 3, 4, 5)) 3 STORE_FAST 0 (x) 3 6 LOAD_FAST 0 (x) 9 LOAD_CONST 2 (2) 12 BINARY_SUBSCR 13 STORE_FAST 1 (y) 16 LOAD_CONST 0 (None) 19 RETURN_VALUE
元组是不可变的,更有内存效率; 列表,为了效率,分配内存,以允许附加没有常量realloc
s.因此,如果您想在代码中迭代一系列常量值(例如for direction in 'up', 'right', 'down', 'left':
),则首选元组,因为这些元组是在编译时预先计算的.
访问速度应该相同(它们都作为连续数组存储在内存中).
但是,当您处理可变数据时,alist.append(item)
它更atuple+= (item,)
受欢迎.请记住,元组旨在被视为没有字段名称的记录.
array
如果列表或元组中的所有项目都是相同的C类型,您还应该考虑标准库中的模块.它将占用更少的内存,并且可以更快.