所谓的“建造速度”比率仅适用于常量元组(其中每个项都由文字表示)。请仔细观察(并在您的计算机上重复 - 您只需在shell /命令窗口中键入命令)...:
$ python3.1 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]'
1000000 loops, best of 3: 0.379 usec per loop
$ python3.1 -mtimeit '[1,2,3]'
1000000 loops, best of 3: 0.413 usec per loop
$ python3.1 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)'
10000000 loops, best of 3: 0.174 usec per loop
$ python3.1 -mtimeit '(1,2,3)'
10000000 loops, best of 3: 0.0602 usec per loop
$ python2.6 -mtimeit -s'x,y,z=1,2,3' '[x,y,z]'
1000000 loops, best of 3: 0.352 usec per loop
$ python2.6 -mtimeit '[1,2,3]'
1000000 loops, best of 3: 0.358 usec per loop
$ python2.6 -mtimeit -s'x,y,z=1,2,3' '(x,y,z)'
10000000 loops, best of 3: 0.157 usec per loop
$ python2.6 -mtimeit '(1,2,3)'
10000000 loops, best of 3: 0.0527 usec per loop
我没在3.0上进行测量,因为我没有它 - 它已经完全过时了,并且没有任何理由保留它,因为3.1在各个方面都比它更优秀(如果您可以升级到Python 2.7,则在每个任务中,Python 2.7的速度几乎比2.6快20% - 而2.6,如您所见,比3.1更快 - 所以,如果您真的关心性能,Python 2.7确实是您应该选择的唯一版本!)。timeit
模块代表您执行循环;-),每次都需要构建一个新的列表对象 - 而且那个构造过程(就像编译器无法轻易地将其识别为编译时的常量和不可变对象的元组的构造)确实需要一点时间。元组在几乎所有方面都比列表表现更好:
1) 元组可以进行常量折叠。
2) 元组可以重复使用,而不是复制。
3) 元组紧凑且不会过度分配。
4) 元组直接引用其元素。
Python的Peephole优化器或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;
Alex提供了很好的答案,但我想进一步扩展一些值得一提的事情。任何性能差异通常都很小,并且与具体的实现有关:因此不要过于依赖它们。
在CPython中,元组以单个内存块存储,因此创建新元组最坏情况下只需要调用一次分配内存的函数。列表使用两个块进行分配:一个固定大小的块包含所有Python对象信息,而另一个块是可变大小的数据块。这就是创建元组更快的部分原因,但这可能也解释了索引速度略有差异,因为少了一个指针需要跟踪。
在CPython中也有优化来减少内存分配:已释放的列表对象保存在空闲列表中,以便可以重复使用,但分配非空列表仍需要为数据分配内存。元组保存在20个用于不同大小元组的空闲列表中,因此分配小元组通常根本不需要调用任何内存分配函数。
像这样的优化在实践中很有帮助,但也可能使过于依赖“timeit”的结果变得有风险,当然如果转移到诸如IronPython之类的其他编程语言中时,则完全不同。
PyObject * PyBLAH_GetItem(PyObject *op, Py_ssize_t i) {return ((PyBLAHObject *)op) -> ob_item[i];}
- John Machinob_item
是结构末尾的一个数组。而在列表中,ob_item
是指向数组的指针。访问这两个数组中的元素的 C 代码是相同的,但在列表的情况下,需要额外的内存读取来获取指针的值。 - DuncanPyObject * ob_item[1];
,而 listobject.h 则有 PyObject ** ob_item;
。 - John Machin借助于timeit
模块的强大功能,您通常可以自行解决与性能有关的问题:
$ python2.6 -mtimeit -s 'a = tuple(range(10000))' 'for i in a: pass'
10000 loops, best of 3: 189 usec per loop
$ python2.6 -mtimeit -s 'a = list(range(10000))' 'for i in a: pass'
10000 loops, best of 3: 191 usec per loop
这表明对于迭代,元组比列表稍微快一些。我得到了类似的结果用于索引,但是对于构建,元组优于列表:
$ python2.6 -mtimeit '(1, 2, 3, 4)'
10000000 loops, best of 3: 0.0266 usec per loop
$ python2.6 -mtimeit '[1, 2, 3, 4]'
10000000 loops, best of 3: 0.163 usec per loop
因此,如果迭代速度或索引是唯一的因素,那么二者实际上没有区别,但在构造方面,元组胜出。
我们无法向元组中添加元素,但是可以向列表中添加元素。
我们无法对元组进行排序,但是在列表中,我们可以通过调用list.sort()
方法进行排序。
我们无法从元组中删除元素,但是在列表中,我们可以删除一个元素。
我们无法替换元组中的元素,但是在列表中,可以进行替换。
sorted((4, 2, 1))
将返回(1, 2, 4)
。 - undefined基本上,元组的不可变性意味着解释器可以使用比列表更轻巧、更快速的数据结构。
列表比元组在从生成器构建时更快,特别是列表推导式比最接近的元组等价物 tuple()
在使用生成器参数时要快得多:
$ python --version
Python 3.6.0rc2
$ python -m timeit 'tuple(x * 2 for x in range(10))'
1000000 loops, best of 3: 1.34 usec per loop
$ python -m timeit 'list(x * 2 for x in range(10))'
1000000 loops, best of 3: 1.41 usec per loop
$ python -m timeit '[x * 2 for x in range(10)]'
1000000 loops, best of 3: 0.864 usec per loop
特别注意,tuple(generator)
似乎比list(generator)
稍微快一点,但是[elem for elem in generator]
比它们两个都要快很多。