元组和列表在元素的实例化和检索方面有什么性能差异吗?


当前回答

Tuple在读取时非常高效的主要原因是因为它是不可变的。

为什么不可变对象容易读取?

原因是元组可以存储在内存缓存中,不像列表。程序总是从列表的内存位置读取,因为它是可变的(可以随时更改)。

其他回答

元组是不可变的,内存效率更高;为了提高速度和效率,列表会过度分配内存,以允许在没有常量reallocs的情况下执行追加操作。所以,如果你想在你的代码中迭代一个常量序列的值(例如在'上','右','下','左':),元组是首选的,因为这样的元组是在编译时预先计算的。

读取访问速度应该相同(它们都作为连续数组存储在内存中)。

但是,在处理可变数据时,list.append(item)比atuple+= (item,)更受欢迎。请记住,元组将被视为没有字段名的记录。

总结

元组几乎在每个类别中都比列表表现得更好:

元组可以被常数折叠。 元组可以重用,而不是复制。 元组是紧凑的,不会过度分配。 元组直接引用它们的元素。

元组可以被常数折叠

常量元组可以通过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;

请注意,tuple对象直接合并了这两个数据指针,而list对象有一个额外的间接层,用于保存这两个数据指针的外部数组。

元组应该比列表更高效,因为它们是不可变的。

通常,您可能希望元组稍微快一点。但是,您一定要测试您的特定情况(如果差异可能会影响程序的性能—记住“过早的优化是万恶之源”)。

Python让这变得非常简单:时间是你的朋友。

$ 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

因此,在这种情况下,元组的实例化几乎快了一个数量级,但列表的项访问实际上要快一些!因此,如果你创建了一些元组,并多次访问它们,实际上,使用列表可能会更快。

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

如果列表或元组中的所有项都是相同的C类型,则还应该考虑标准库中的array模块。它占用的内存更少,运行速度更快。