Python中的列表和数组

列表和元组基础

列表和元组都是可以放置任意数据类型的有序集合。

1
2
3
4
5
6
7
l = [1, 2, 'hello', 'world'] # 列表中同时含有 int 和 string 类型的元素
l
[1, 2, 'hello', 'world']

tup = ('jason', 22) # 元组中同时含有 int 和 string 类型的元素
tup
('jason', 22)

两者的区别如下:

  • 列表是动态的,长度大小不固定,可以随意增加、删除或者更改元素(mutable)。
  • 元组是动态的,长度大小固定,无法增加删除或者改变(immutable)。
1
2
3
4
5
6
7
8
9
10
l = [1, 2, 3, 4]
l[3] = 40 # 和很多语言类似,python 中索引同样从 0 开始,l[3] 表示访问列表的第四个元素
l
[1, 2, 3, 40]

tup = (1, 2, 3, 4)
tup[3] = 40
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

如果我们想修改元组中的元素,就需要重新申请一片内存,但对于列表,我们就可以直接插入新的元素:

1
2
3
4
5
6
7
8
9
tup = (1, 2, 3, 4)
new_tup = tup + (5, ) # 创建新的元组 new_tup,并依次填充原元组的值
new _tup
(1, 2, 3, 4, 5)

l = [1, 2, 3, 4]
l.append(5) # 添加元素 5 到原列表的末尾
l
[1, 2, 3, 4, 5]

列表和元组的存储方式

列表是动态的,元组是静态的,这一差别会影响其存储方式:

1
2
3
4
5
6
l = [1, 2, 3]
l.__sizeof__()
64
tup = (1, 2, 3)
tup.__sizeof__()
48

由于列表要存储可变长度的元素,所以需要额外的指针来存储下一个元素的位置(int型的指针占8个字节);除此之外,还需要存储已经分配的长度大小(8个字节),因而相对于元组来说,列表要消耗更多的内存。对于列表来说,当空间不足时会重新分配新的空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
l = []
l.__sizeof__() // 空列表的存储空间为 40 字节
40
l.append(1)
l.__sizeof__()
72 // 加入了元素 1 之后,列表为其分配了可以存储 4 个元素的空间 (72 - 40)/8 = 4
l.append(2)
l.__sizeof__()
72 // 由于之前分配了空间,所以加入元素 2,列表空间不变
l.append(3)
l.__sizeof__()
72 // 同上
l.append(4)
l.__sizeof__()
72 // 同上
l.append(5)
l.__sizeof__()
104 // 加入元素 5 之后,列表的空间不足,所以又额外分配了可以存储 4 个元素的空间

为了减少每次插入元素时反复申请空间所带来的额外开销,Python会给列表分配额外的空间,这样的机制称为(Over-allocating)。而对于元组来说,由于存储的数目不可变,因而空间大小固定。

两者的存储差距在面对巨量的数据规模时会更加明显。

列表和元组的性能

因为存储方式上的差异,总体上来说,元组的性能要优于列表。

Python在进行内存管理时,会对静态数据进行资源缓存。对于Python来说,如果一些变量不再被使用时,便会调用垃圾回收机制对这些变量进行回收。但对于元组等静态变量,当其不再被使用且大小不大的情况下,Python会对其进行缓存,从而在下次申请同样大小的内存时直接对缓存的空间进行操作,而不用返回申请内存,进而提升了效率。

列表和元组的适用场景

  1. 如果所要存储的数据不大且不变时,选择元组更合适;
  2. 如果数据需要经常改动,选择列表更合适。

思考

list()[]创建列表的区别:

区别主要在于list()是一个function call,Python的function call会创建stack,并且进行一系列参数检查的操作,比较expensive,反观[]是一个内置的C函数,可以直接被调用。

参考