元组在Python中比列表更高效吗?

321 投票
9 回答
141181 浏览
提问于 2025-04-11 09:14

在创建和获取元素时,元组和列表之间有没有性能差异?

9 个回答

230

一般来说,你可能会觉得元组(tuple)会稍微快一点。不过,你一定要测试一下你具体的情况(如果这个差异会影响你程序的性能——记住“过早优化是万恶之源”)。

在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

所以在这个例子中,创建元组的速度几乎快了一个数量级,但访问列表中的项目实际上要快一些!所以如果你创建几个元组,但要访问它们很多次,使用列表可能会更快。

当然,如果你想要改变某个项目,列表肯定会更快,因为你需要创建一个全新的元组来改变其中的一个项目(因为元组是不可变的)。

368

总结

元组在几乎所有方面的表现都比列表好

  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;

注意,元组对象直接包含两个数据指针,而列表对象则有一个额外的间接层,指向一个外部数组,该数组保存这两个数据指针。

208

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

撰写回答