更多内容,欢迎关注微信公众号: tmac_lover
使用python的过程中,内置数据结构一定是使用频率最多的,比如元组(tuple), 列表(list), 字典(dict),集合(set)。这些数据结构的底层实现都经过了很好的优化,性能都非常的好。当然python提供的这些不同的数据结构,都是各有优势,也各有各自的适合场景。今天先介绍一下元组(tuple)和列表(list)这两种常用的数据结构。
列表和元组在python中都是用来存放任意数据类型的有序集合,它们两个的区别在于:
下面同样通过增加操作可以感受到两者的差别:
tup = (1,2,3)
l = [1,2,3]
l.append(4)
# l的值是[1,2,3,4]
new_tup = tup + (4,)
# tup的值仍然是(1,2,3), 而new_tup的值是(1,2,3,4)
可以看到,如果想往元组里增加值,只能创建一个新的元组。
列表和元组因为一个可变,一个不可变,所以其两者的存储方式也存在差异。这也会造成两者的存储空间大小的不同:
l = [1,2,3]
tup = (1,2,3)
print(l.__sizeof__())
print(tup.__sizeof__())
上面代码的输出结果是;
64
48
可以看出,存储相同的元素时,列表的大小比元组要大。这是因为list是动态的,它需要存储指针来指向对应的元素。另外,因为列表是可变的,为了保存插入
的高效,一般会预留额外的存储空间,因此就需要记录实时的空间使用情况,当空间不足时,额外再分配空间, 看下面的代码:
l=[]
print(l.__sizeof__()) # 输出40,表明一个空的List占40个字节的内存
l.append(1)
print(l.__sizeof__()) # 输出是72,添加一个元素后,列表额外多分配了一些空间
l.append(2)
print(l.__sizeof__()) # 输出仍然是72,因为之前已经额外多分配了空间
l.append(3)
print(l.__sizeof__()) # 输出仍然是72,和上面一样
l.append(4)
print(l.__sizeof__()) # 输出仍然是72,和上面一个的原因
l.append(5)
print(l.__sizeof__()) # 输出变成104,因为之前分配的空间已用完,这时就会额外多分配一些以后可以接着使用
通过上面的例子可以看到,为了避免每次新增元素的时候都要重新开僻内存空间和内存搬移,python在列表新增元素时,会一次性的多开僻一些空间,这样的操作
可以保证列表增加元素时的高效性,因此列表增加/删除元素的平均时间复杂度为O(1)。
对于元组,由于其不可变的特性,因此它的存储空间也是固定的。
从上面我们可以感觉到,元组相对列表来说,在存储空间上显得更加轻量级一些。另外,由于python的垃圾回收机制,对于没有引用的变量占用的内存,python
会将这些内存回收,交还给操作系统。但是对于元组这样的静态变量,在占用空间不是特别大的情况下,python并不会回收,而是保留等到下一次如果需要使用差不
多大的内存时,直接复用,这样就减少了向操作系统去重新申请内存的开销,效率也会更高,我们可以比较一下:
%timeit x=(1,2,3,4)
在Jupyter notebook里,这段的返回值是
13.1 ns ± 0.285 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
再看一下列表的情况:
%timeit l=[1,2,3,4]
输出结果是:
50.9 ns ± 0.747 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
比较可以看到,元组的初始化速度比列表要快了不少。
对于按下标索引操作,列表和元组的性能是差不多的,至于修改,删除,添加,上面有讨论过,列表的性能更优。
对元组和列表,最常用的就是遍历操作, 很多人喜欢用这种写法:
x = (1,2,3)
for index in range(len(x)):
print(x[index])
这样写一是不够优雅,第二个就是在速度上稍显不足,更好的一种写法是使用enumerate():
x = (1,2,3)
for index, val in enumerate(x):
print(val)
对于列表,有一个很有意思的问题,在创建列表时一般有两种方式:
l = list()
ll = []
有没有想过哪一种更快一些呢。实测看一下:
%timeit l = list()
%timeit ll = []
输出结果分别是:
87.1 ns ± 1.07 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
26.6 ns ± 4.41 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
可以看到,使用[]这种方式创建一个列表的速度,明显优于list()的方式。这是因为list()实际上是一个python函数,函数调用就会涉及到堆栈等的调用,而[]是由python解释器直接实现的一个C函数,性能会更加优一些。