以下内容是整理极客时间Python核心技术与实战课程的笔记。
列表(list)和元组(tuple)是Python最常用两种基本结构
列表和元组的基本概念
1、那么列表和元组是什么呢?
列表和元素都是一个可以放置任意数据类型的有序集合。
2、那么他们的异同呢?
举个例子,Python中的列表和元组同时可以存着int或者string类型的元素,其他语言则必须类型一致。例子如下。
list = [1, 2, 'hello', 'world'] # 列表中同时含有 int 和 string 类型的元素
tup = ('jason', 22) # 元组中同时含有 int 和 string 类型的元素
print(list)
print(tup)
# [1, 2, 'hello', 'world']
# ('jason', 22)
这个是Python中的共性,当然,他们也有不同点。
具体的区别如下:
- 列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素(mutable)。
- 而元组是静态的,长度大小固定,无法增加删减或者改变(immutable)。
通过一个例子来说明。
对于列表,可以随意的将最后一个值重新赋值,但是对于元组来说,这样的操作,Python就报错,原因是tuple不可变。
list = [2, 3, 6, 8]
list[3] = 40 # 和很多语言类似,python 中索引同样从 0 开始,l[3] 表示访问列表的第四个元素
print(list)
[2, 3, 6, 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
那么如何改变已有元组的值呢?
若相对已有的元组改变值,那么就需要重新申请内存,此时,就只能创建一个新的元组,也就是说,如果要给原来元组新加一个值,那么其实是重新创建了一个元组,然后在原来的元组的基础上增加了一个值,具体操作如下。
tup = (1, 2, 3, 4)
new_tup = tup + (5, ) # 创建新的元组 new_tup,将原来的值补充上
print(new_tup)
# (1, 2, 3, 4, 5)
对于列表,直接追加即可
list_demo = [1, 2, 3, 4]
list_demo.append(5)
print(list_demo)
# [1, 2, 3, 4, 5]
元组和列表使用的基本操作
1、负数索引
Python是支持负数索引,-1表示最后一个元素,-4表示倒数第四个元素。举个例子。
list_demo = [1, 2, 3, 4]
print(list_demo[-1])
# 4
tup_demo = (1, 2, 3, 4)
print(tup_demo[-1])
# 4
2、切片操作
列表和元素都支持切片操作
list_demo = [1, 2, 3, 4]
print(list_demo[1:3])
# [2, 3]
tup_demo = (1, 2, 3, 4)
print(tup_demo[1:3])
# (2, 3)
3、随意嵌套
具体请看例子
# 列表里面的每一个元素也是一个列表
list_demo = [[1, 2, 3], [4, 5]]
# 元组的每一个元素也是一元组
tup_demo = ((1, 2, 3), (4, 5, 6))
4、相互转换
两者也可以通过 list() 和 tuple() 函数相互转换
# 用list将一个元组转成列表
list((1, 2, 3))
# [1, 2, 3]
# 用一个tuple元素将一个列表转成元组
tuple([1, 2, 3])
# (1, 2, 3)
5、常用内置函数
- count(item) 表示统计列表 / 元组中 item 出现的次数。
- index(item) 表示返回列表 / 元组中 item 第一次出现的索引。
- list.reverse() 和 list.sort() 分别表示原地倒转列表和排序(元组没有内置的这两个函数)。
- reversed() 和 sorted()同样表示对列表 / 元组进行倒转和排序,但是会返回一个倒转后或者排好序的新的列表 / 元组。
举个例子
list_demo = [3, 2, 3, 7, 8, 1]
list_demo.count(3) # 统计value=3的元素个数
# 2
list_demo.index(7) # 元素中7第一次出现的下标
# 3
list_demo.reverse() # 列表转置
# [1, 8, 7, 3, 2, 3]
list_demo.sort() # 列表排序,从小到大
print(list_demo)
# [1, 2, 3, 3, 7, 8]
tup = (3, 2, 3, 7, 8, 1)
tup.count(3)
# 2
tup.index(7)
# 3
list(reversed(tup)) # 元组的转置
# [1, 8, 7, 3, 2, 3]
sorted(tup) # 元组的排序
# [1, 2, 3, 3, 7, 8]
列表和元组的存储方式的区别
列表和元组的最大区别就是一个是可变的,一个是静态的,那么他们之间的存储方式的区别是什么?
对于下面这个例子来说,对于元组和列表,放置相同的元素,但是元组的存储空间比列表占用的小。
list_demo = [1, 2, 3]
list_demo.__sizeof__()
# 64
tup_demo = (1, 2, 3)
tup_demo.__sizeof__()
# 48
那为什么相同元素列表的存储空间占用比元组的大呢?
由于列表是动态的,那么它就需要需要存储指针来指向对应的元素,对于int类型的数据,占用8个字节,同时,需要额外存储已经分配的长度大小,也是8个字节,如上面这个例子,这样子做的目的就是,能检测当列表空间不够的时候,重新及时的分配额外的空间大小。
list_demo = []
list_demo.__sizeof__() # 空列表的存储空间为 40 字节
# 40
list_demo.append(1)
list_demo.__sizeof__()
# 72 加入了元素 1 之后,列表为其分配了可以存储 4 个元素的空间 (72 - 40)/8 = 4
list_demo.append(2)
list_demo.__sizeof__()
# 72 由于之前分配了空间还用,不会去重新分配内存,所以加入元素 2,列表空间不变
list_demo.append(3)
list_demo.__sizeof__()
# 72
list_demo.append(4)
list_demo.__sizeof__()
# 72
list_demo.append(5)
list_demo.__sizeof__()
# 104 加入元素 5 之后,列表的空间不足,默认分配的4个空间用完了,所以又额外分配了可以存储 4 个元素的空间
在上面例子,发现一个问题。
为什么Python 每次分配空间时都会额外多分配一些?
这样是为了列表动态添加元素的时候,为了减小每次增加 / 删减操作时空间分配的开销。保证了其操作的高效性:增加 / 删除的时间复杂度均为 O(1)。这样的机制称为over-allocating。
对于元组元组长度大小固定,元素不可变,所以存储空间固定。
列表和元组的性能
通过上面的例子我们可以知道,元组要比列表更加轻量级一些,所以总体上来说,元组的性能速度要略优于列表。
Python 会在后台对静态数据做一些资源缓存,通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,Python 就会回收它们所占用的内存,返还给操作系统,以便其他变量或其他应用使用。
但是对于一些静态变量,比如元组,如果它不被使用并且占用空间不大时,Python 会暂时缓存这部分内存。这样,下次我们再创建同样大小的元组时,Python 就可以不用再向操作系统发出请求,去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。
第一个例子,计算初始化一个相同元素的列表和元组分别所需的时间。我们可以看到,元组的初始化速度,要比列表快 5 倍。
python3 -m timeit 'x=(1,2,3,4,5,6)'
20000000 loops, best of 5: 9.97 nsec per loop
python3 -m timeit 'x=[1,2,3,4,5,6]'
5000000 loops, best of 5: 50.1 nsec per loop
第二个例子,对于索引操作的话,两者的速度差别非常小,几乎可以忽略不计。
python3 -m timeit -s 'x=[1,2,3,4,5,6]' 'y=x[3]'
10000000 loops, best of 5: 22.2 nsec per loop
python3 -m timeit -s 'x=(1,2,3,4,5,6)' 'y=x[3]'
10000000 loops, best of 5: 21.9 nsec per loop
如果你想要增加、删减或者改变元素,那么列表显然更优。因为对于元组,你必须得通过新建一个元组来完成。
列表和元组的使用场景
- 如果存储的数据和数量不变,比如你有一个函数,需要返回的是一个地点的经纬度,然后直接传给前端渲染,那么肯定选用元组更合适。
- 如果存储的数据或数量是可变的,比如社交平台上的一个日志功能,是统计一个用户在一周之内看了哪些用户的帖子,那么则用列表更合适。
一些有关list和tuple的问题
1、下面两种方式,那种创建一个空列表的效率更好?
empty_list = list()
empty_list = []
[] 的创建方法是直接调用内置的c函数, 可以直接调用,效率高,而list()的创建方式,需要Python做出来,效率较低。