重学Python: 01列表和元组
文章目录
1. 什么是列表和元组
列表和元组,都是一个可以放置任意数据类型的有序集合
l = [1, 2, 3, 'a', 'abc', 'hhhe']
l
[1, 2, 3, 'a', 'abc', 'hhhe']
tup = (111, 'hello', 'WORLD')
tup
(111, 'hello', 'WORLD')
2. 列表和元组的区别
列表是动态的,长度大小不固定,可以随意地增加、删减或者改变元素(mutable)
元组是静态的,长度大小固定,无法增加删减或者改变(immutable)
l = [1, 2, 3, 'a','abc', "hhhe" ]
l.append('CSUST')
l
[1, 2, 3, 'a', 'abc', 'hhhe', 'CSUST']
l[2] = 888
l
[1, 2, 888, 'a', 'abc', 'hhhe', 'CSUST']
tup = (111, 'hello', 'WORLD')
tup[-1]
'WORLD'
tup[0] = 1
tup
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-31dca4ff425f> in <module>()
1 tup = (111, 'hello', 'WORLD')
----> 2 tup[0] = 1
3 tup
TypeError: 'tuple' object does not support item assignment
- 对于列表进行元素修改可以操作,因为列表是动态的、可变的
- 对于元组不可以进行修改操作, tuple 一旦初始化就不能修改。 也就是说元组(tuple)是不可变的。
元组(tuple) 不可变是指当你创建了 tuple 时候,它就不能改变了,也就是说它也没有 append(),insert() 这样的方法,但它也有获取某个索引值的方法,但是不能赋值。 - 那么为什么要有 tuple 呢?
那是因为 tuple 是不可变的,所以代码更安全。
所以建议能用 tuple 代替 list 就尽量用 tuple
3. 列表和元组的创建/访问/修改
3.1 创建
列表的创建: list = [1 , 2, 3, ‘222’, ‘2233’]
其实列表就是用中括号 []
括起来的数据,里面的每一个数据就叫做元素。每个元素之间使用逗号分隔。
而且列表的数据元素不一定是相同的数据类型。
元组的创建: tup = (1,2,‘222’)
元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。
tuple1=('两点水','twowter','liangdianshui',123,456)
tuple2='两点水','twowter','liangdianshui',123,456
创建空元组
tuple3=()
元组中只包含一个元素时,需要在元素后面添加逗号
tuple4=(123,)
如果不加逗号,创建出来的就不是 元组(tuple),而是指 123
这个数了。
这是因为括号 () 既可以表示元组(tuple),又可以表示数学公式中的小括号,这就产生了歧义。
所以如果只有一个元素时,你不加逗号,计算机就根本没法识别你是要进行整数或者小数运算还是表示元组。
因此,Python 规定,这种情况下,按小括号进行计算,计算结果自然是 123
,而如果你要表示元组的时候,就需要加个逗号。
tuple4=(123,)
tuple5=(123)
print(tuple4)
print(tuple5)
#输出:
(123,)
123
3.2 访问
列表和元组中元素都可以通过索引和切片进行访问
name = ['一点水', '两点水', '三点水', '四点水', '五点水']
# 通过索引来访问列表
print(name[2])
# 通过方括号的形式来截取列表中的数据
print(name[0:2])
name = ()'一点水', '两点水', '三点水', '四点水', '五点水')
# 通过索引来访问列表
print(name[2])
# 通过方括号的形式来截取列表中的数据
print(name[0:2])
切片访问时,元素区间是左闭右开区间的。
所以 name[0:2]
的意思就是从第 0 个开始取,取到第 2 个,但是不包含第 2 个。
3.3 修改
列表的修改
name = ['一点水', '两点水', '三点水', '四点水', '五点水']
# 通过索引对列表的数据项进行修改或更新
name[1]='2点水'
print(name)
# 使用 append() 方法来添加列表项
name.append('六点水')
print(name)
['一点水', '2点水', '三点水', '四点水', '五点水']
['一点水', '2点水', '三点水', '四点水', '五点水', '六点水']
删除 List(列表) 里面的元素
使用 del 语句来删除列表的的元素
name = ['一点水', '两点水', '三点水', '四点水', '五点水']
print(name)
# 使用 del 语句来删除列表的的元素
del name[3]
print(name)
['一点水', '两点水', '三点水', '四点水', '五点水']
['一点水', '两点水', '三点水', '五点水']
修改元组 (tuple)
可能看到这个小标题有人会疑问,上面不是说 tuple 是不可变的吗?
这里怎么又来修改 tuple (元组) 了。
那是因为元组中的元素值是不允许修改的,但我们可以对元组进行连接组合,还有通过修改其他列表的值从而影响 tuple 的值。
具体看下面的这个例子:
#-*-coding:utf-8-*-
list1=[123,456]
tuple1=('两点水','twowater','liangdianshui',list1)
print(tuple1)
list1[0]=789
list1[1]=100
print(tuple1)
list1.append(90)
print(tuple1)
输出的结果:
('两点水', 'twowater', 'liangdianshui', [123, 456])
('两点水', 'twowater', 'liangdianshui', [789, 100])
('两点水', 'twowater', 'liangdianshui', [789, 100, 90])
可以看到,两次输出的 tuple 值是变了的。
可以看到,tuple1 有四个元素,最后一个元素是一个 List ,List 列表里有两个元素。
当我们把 List 列表中的两个元素 124
和 456
修改为 789
和 100
的时候,从输出来的 tuple1 的值来看,好像确实是改变了。
但其实变的不是 tuple 的元素,而是 list 的元素。
tuple 一开始指向的 list 并没有改成别的 list,所以,tuple 所谓的“不变”是说,tuple 的每个元素,指向永远不变。注意是 tupe1 中的第四个元素还是指向原来的 list ,是没有变的,我们修改的只是列表 List 里面的元素。
这个就相当C语言中的char * const p; //指针指向不可改变但是指针里面的内容却是可以改变的。
const char*p; //指针指向的内容不可改变
const char* const p; //指针内容和指针指向均不可以改变
删除元组 (tuple)
tuple 元组中的元素值是不允许删除的,但我们可以使用 del 语句来删除整个元组
#-*-coding:utf-8-*-
tuple1=('两点水','twowter','liangdianshui',[123,456])
print(tuple1)
del tuple1
4. 列表和元组的相关接口
4.1 list(列表)运算符
列表对 +
和 *
的操作符与字符串相似。+
号用于组合列表,*
号用于重复列表。
Python 表达式 | 结果 | 描述 |
---|---|---|
len([1, 2, 3]) | 3 | 计算元素个数 |
[1, 2, 3] + [4, 5, 6] | [1, 2, 3, 4, 5, 6] | 组合 |
[‘Hi!’] * 4 | [‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’] | 复制 |
3 in [1, 2, 3] | True | 元素是否存在于列表中 |
for x in [1, 2, 3]: print x, | 1 2 3 | 迭代 |
4.2 list (列表)函数&方法
函数&方法 | 描述 |
---|---|
len(list) | 列表元素个数 |
max(list) | 返回列表元素最大值 |
min(list) | 返回列表元素最小值 |
list(seq) | 将元组转换为列表 |
list.append(obj) | 在列表末尾添加新的对象 |
list.count(obj) | 统计某个元素在列表中出现的次数 |
list.extend(seq) | 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表) |
list.index(obj) | 从列表中找出某个值第一个匹配项的索引位置 |
list.insert(index, obj) | 将对象插入列表 |
list.pop(obj=list[-1]) | 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值 |
list.remove(obj) | 移除列表中的一个元素(参数是列表中元素),并且不返回任何值 |
list.reverse() | 反向列表中元素 |
list.sort([func]) | 对原列表进行排序 |
4.3 tuple(元组)运算符
与字符串一样,元组之间可以使用 +
号和 *
号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。
Python 表达式 | 结果 | 描述 |
---|---|---|
len((1, 2, 3)) | 3 | 计算元素个数 |
(1, 2, 3) + (4, 5, 6) | (1, 2, 3, 4, 5, 6) | 连接 |
(‘Hi!’,) * 4 | (‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’) | 复制 |
3 in (1, 2, 3) | True | 元素是否存在 |
for x in (1, 2, 3): print(x) | 1 2 3 | 迭代 |
4.4 tuple(元组)内置函数
方法 | 描述 |
---|---|
len(tuple) | 计算元组元素个数 |
max(tuple) | 返回元组中元素最大值 |
min(tuple) | 返回元组中元素最小值 |
tuple(seq) | 将列表转换为元组 |
注意:
- list.reverse() 和 list.sort() 分别表示原地倒转列表和排序(注意,元组没有内置的这两个
函数, 元组是不可变化的) - reversed() 和 sorted() 同样表示对列表 / 元组进行倒转和排序,但是会返回一个倒转后
或者排好序的新的列表 / 元组
5. 列表和元组存储方式的差异
根据前面的叙述,我们知道列表和元组最重要的区别就是,列表是动态的、可变的,而元组是静态的、不可
变的。这样的差异,势必会影响两者存储方式。我们可以来看下面的例子 :
内置函数 __sizeof__() : 打印系统分配空间的大小
l = [1, 2, 3]
len1 = l.__sizeof__()
tup = (1,2,3)
len2 =tup.__sizeof__()
print(len1)
print(len2)
64
48
对比发现,对列表和元组,我们放置了相同的元素,但是元组的存储空间,却比列表要少
16 字节 。
事实上,由于列表是动态的,所以它需要存储指针,来指向对应的元素(上述例子中,对于
int 型,8 字节)。另外,由于列表可变,所以需要额外存储已经分配的长度大小(8 字
节),这样才可以实时追踪列表空间的使用情况,当空间不足时,及时分配额外空间
l = []
len1 = l.__sizeof__()
print(len1)
l.append(1)
len1 = l.__sizeof__()
print(len1)
l.append(2)
len1 = l.__sizeof__()
print(len1)
l.append(3)
len1 = l.__sizeof__()
print(len1)
l.append(4)
len1 = l.__sizeof__()
print(len1)
l.append(5)
len1 = l.__sizeof__()
print(len1)
#输出
40
72
72
72
72
104
上面的例子,大概描述了列表空间分配的过程。我们可以看到,为了减小每次增加 / 删减
操作时空间分配的开销,Python 每次分配空间时都会额外多分配一些,这样的机制
(over-allocating)保证了其操作的高效性[Redis中也存在预分配内存的用法]:增加 / 删除的时间复杂度均为 O(1)
但是对于元组,情况就不同了。元组长度大小固定,元素不可变,所以存储空间固定
6. 列表和元组的性能
通过学习列表和元组存储方式的差异,我们可以得出结论:元组要比列表更加轻量级一些,
所以总体上来说,元组的性能速度要略优于列表.
另外,Python 会在后台,对静态数据做一些资源缓存(resource caching)。通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,Python 就会回收它们所占用的内存,返还给操作系统,以便其他变量或其他应用使用。
但是对于一些静态变量,比如元组,如果它不被使用并且占用空间不大时,Python 会暂时缓存这部分内存。这样,下次我们再创建同样大小的元组时,Python 就可以不用再向操作系统发出请求,去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度
下面的例子,是计算初始化一个相同元素的列表和元组分别所需的时间。我们可以看到,元组的初始化速度,要比列表快 5
倍
import timeit
print(timeit.timeit(stmt="tup = (1,2,3,4,5,6)", number=1000000))
print(timeit.timeit(stmt="l = [1,2,3,4,5,6]", number=1000000))
##输出
0.015461600000000075
0.06565259999999995
但如果是索引操作的话,两者的速度差别不大,在同一个数量级上, 几乎可以忽略不计
import timeit
code1 = """
tup = (1,2,3,4,5,6)
y = tup[2]
"""
code2 = """
l = [1,2,3,4,5,6]
y = l[4]
"""
print(timeit.timeit(stmt=code1, number=10000000))
print(timeit.timeit(stmt=code2, number=10000000))
##输出
0.3606373000000076
0.8088017999999977
如果测试增加、删减或者改变元素,那么列表显然更优。
因为对于元组,必须得通过新建一个元组来完成 。
7. 列表和元组的使用场景
- 如果存储的数据和数量不变,比如你有一个函数,需要返回的是一个地点的经纬度,然
后直接传给前端渲染,那么肯定选用元组更合适 - 如果存储的数据或数量是可变的,比如微博头条日志功能,需要统计一个用户在
一周之内看了哪些用户的帖子,那么则用列表更合适 。