Python中的列表和元组
1.列表List
问题:现在要保存100个人的姓名,如何操作?
答案是将这100个人的姓名存放在列表中。
列表是一种可变的序列类型,能够存储多个元素。
列表的特点:
- 列表中元素可以是多种数据类型。
- 列表中的元素的有序的,因此用类似于数组下标的方式进行索引访问。
- 列表是可变类型,列表内的元素可以修改。
1.1 创建列表
列表创建主要有两种方式:
- 直接使用一对
[]
即可,里面内部可以放上需要放入的元素。 - 通过
list()
函数来构造列表,函数无参数时创建空列表,有参数时,例如传入一个元组,可以将该元组转换成列表。
定义列表非常简单,使用一对[]
即可。例如定义一个空的列表:
elist = []
当然也可以定义非空的列表,元素之间使用逗号,
分隔:
lis = [1, 2, 3, 4]
在Python
中,列表中的元素类型可以是不同的:
lis = [1, 2.3, 5 + 8j, “apple”]
通过list()
函数构造列表:
a = (1,2,3)
b = list(a)
print(b)
# output
# [1, 2, 3]
1.2 列表运算符
列表(序列)也是支持部分运算,因为列表可以包含多个元素,相比之前学习的数值类型(仅包含单个值)来说,其计算规则相对复杂一些。
列表支持如下的运算:
符号 | 说明 |
---|---|
x + y | 返回连接,合并x与y两个列表后的新列表,x,y本身不变 |
x * n | 返回将序列重复n次(所有元素重复n次)。 |
e in x | 如果e在x中,返回True,否则返回False。 |
e not in x | 如果e不在x中,返回True,否则返回False。 |
x 比较 y | 依次比较x,y的元素(>,>=,<,<=,==,!=)。 |
x is,is not y | 判断x,y是否为同一个列表。 |
x 布尔 y | 与数值类型的布尔运算(and或or)规则相同(返回两个列表中的一个) |
说明:
假设上表中,x与y是列表类型,e为列表中的元素,n为整数。
1.3 列表索引
定义列表后可以通过索引来访问列表的元素。访问方式为:
列表名[索引值]
索引是从0开始编号的,最大索引值为列表的长度- 1,列表(序列)的长度可以通过len()
函数获得。
同时,列表的索引也支持负值,表示倒数第n个位置。例如:list[-1]
表示倒数第一个(最后一个)元素。列表的有效索引值区间为[-length, length - 1]
(length为列表的长度)。如果索引不在有效区间,将会产生错误。
列表是可变的序列类型,我们除了可以通过索引获取元素的值,也可以改变元素的值。
1.4 列表切片
切片可以用来操作列表(序列)一个区间的元素。切片的语法为:
列表名[start : end : step]
start
,end
与step
都是可选的,也都可以是负值。
注意切片截取元素的方向。两个端点同时存在,与两个端点缺失一个(或全部缺失),表现的不同。通过切片也可以修改列表的一个区间。
1.5 列表相关方法
-
append(obj)
:在原列表尾部添加一个元素。 -
insert(index, obj)
:在列表索引位置index处插入一个元素。 -
remove(value)
:删除列表中第一个value元素。 -
pop([index])
:将index处的元素从列表中移除并返回,不写则默认最后一个元素。 -
index(value, [start, stop])
:返回value的索引位置。 -
count(value)
:返回列表中value的个数。 -
reverse()
:逆序列表。 -
extend(iterable)
:可以一次添加多个元素。注意添加字符串等可迭代对象时会拆开来添加。 -
clear()
:清空列表。 -
copy()
:复制列表,浅拷贝。 -
sort(key=None, reverse=False)
:给列表排序。 -
del()
:除了可以删除变量名,也可以用来删除列表的元素。例如:del li
,del li[0]
,del[0:3]
。
1.6 列表的复制
列表的内存存储:
id(value)
函数:返回value
的地址。- 浅拷贝:列表的
copy
方法。copy
模块的copy()
方法,只拷贝父对象,不会拷贝对象的内部的子对象 - 深拷贝:
copy
模块的deepcopy()
方法,拷贝对象及其子对象
示例:
import copy
a = [1, 2, 3, 4, ['a', 'b']] #原始对象
b = a #赋值,传对象的引用
c = copy.copy(a) #对象拷贝,浅拷贝
d = copy.deepcopy(a) #对象拷贝,深拷贝
a.append(5) #修改对象a
a[4].append('c') #修改对象a中的['a', 'b']数组对象
print ('a = ', a)
print ('b = ', b)
print ('c = ', c)
print ('d = ', d)
# output
'''
a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c = [1, 2, 3, 4, ['a', 'b', 'c']]
d = [1, 2, 3, 4, ['a', 'b']]
'''
1.7 列表推导式
列表推导式是一种创建列表的简洁方式。
当我们需要创建一个列表,该列表中的每个元素是通过另外一个列表(或可迭代对象)中的每个元素(或部分元素)经过运算获得时(即当需要创建的列表元素依赖于另外一个可迭代对象的元素时),就可以考虑使用列表推导式来实现。
列表推导式的语法为:
[表达式 for 更多的for或if]
如,将l1中的每个元素乘以2然后保存在l2中:
l1=[1,2,3,4]
l2=[x*2 for x in l1]
如,将l1中的每个大于2的元素乘以2然后保存在l2中:
l1=[1,2,3,4]
l2=[x*2 for x in l1 if x>2]
列表推导式中的变量:
for
循环中的变量会在for
循环后继续有效(这可能不是我们期望的),而列表推导式中声明的变量仅在列表推导式中有效(如果赋值,与左侧变量也不会冲突)。
1.8 列表的遍历
列表具有索引,可以使用while
循环遍历。序列类型都是可迭代对象,因此,序列可以使用在for
循环中进行遍历。
列表是可以嵌套的,即列表中的元素依然是列表类型。例如,如果保存一个班级的所有学生的名字,是一个一级列表,可以要保存每个学生所选修的课程,就是一个二级列表了。
2.元组
2.1 定义与基本操作
元组是一种不可变的序列。即元组一经创建,就无法再进行修改,试图对元组进行的任何修改均会产生错误。除了不可变性,元组与列表非常相似,可以近似的认为,元组是一种不可变的列表类型。
创建元组:
元组使用()
表示,多个元素使用逗号,
分隔。但是当元组仅有一个元素时,必须在元素后面加上逗号,
,否则不是元组类型。(重要的是逗号,不是小括号)
以下情况下,元组需要使用()
:
- 长度为0的元素(空元组)
- 元组嵌套
- 元组作为表达式的一部分
然而,为了令程序更加具有可读性,建议当使用元组时,总是使用()
。
元组的操作:
- 索引
- 切片
- 运算
- 遍历
2.2 相关方法
因为元组是不可变的序列,因此所有修改序列的方法都不适合元组。
count(value)
index(value, [start, [stop]])
2.3 元组的意义
元组作为一种不可变序列,可以保证数据在程序运行过程中不会意外由自己或其他人修改(例如在函数参数传递中),这样可以保证数据的完整性。
注意,元组的元素不能修改,指的是元组当前的元素,如果元素本身是 一个列表,那么列表中的元素是可以修改的,比如下面这段代码:
tup = ([1,2,3],1,2)
tup[0][1]=4
print(tup)
# output:
# ([1, 4, 3], 1, 2)
3. 列表与元组总结
列表和元组,都是一个可以放置任意数据类型的有序集合。在绝大多数编程语言中,集合内的数据类型必须一致,但是对于Python
的列表和元组来说,并无此要求。
它们的区别是:
- 列表是动态的,长度大小不固定,可以随意地增加、删改或者修改其中的元素(mutable)。
- 元组是静态的,长度大小固定,不可以增加、删改或者修改其中的元素(immutable),想对已有的元组做任何”改变”,就只能重新开辟一块内存,创建新的元组了。
- 列表和元组两者可以通过
list()
和tuple()
函数相互转换。
列表和元组的性能比较:
元组要比列表更加轻量级一些,所以总体上来说元组的性能速度要略优于列表。另外Python
会在后台,对静态数据做一些资源缓存(resource caching)。通常来说,因为垃圾回收机制的存在,如果一些变量不被使用了,Python
就会回收它们所占用的内存并返还给操作系统,以便其他变量或其他应用使用。但是对于一些静态变量,比如元组,如果它不被使用并且占用空间不大时,Python
会暂时缓存这部分内存。这样,下次我们再创建同样大小的元组时,Python
就可以不用再向操作系统发出请求,去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。
下面的例子,是计算初始化一个相同元素的列表和元组分别所需的时间。可以看到,元组的初始化速度,要比列表快5倍:
import timeit
print("The time taken is ",timeit.timeit(stmt='x=(1,2,3,4,5,6)'))
print("The time taken is ",timeit.timeit(stmt='x=[1,2,3,4,5,6]'))
# output:
# The time taken is 0.019765700000000663
# The time taken is 0.06865189999999899
但如果是索引操作的话,两者的速度差别非常小,几乎可以忽略不计。
思考:创建空列表有以下两种方式,哪一种更快一些?
empty_list = list()
empty_list = []
上面两种方式的区别主要在于list()
是一个函数调用(function call),Python
的function call会创建栈stack,并且进行一系列参数检查的操作,比较expensive,反观[]
是一个内置的C函数,可以直接被调用,因此效率更高。
4.序列概念与分类
序列是一种可迭代对象,可以存储多个数据,并提供数据的访问。序列中的数据,我们称为序列的元素。Python
中,内置的序列类型有:
列表(list)
元组(tuple)
字符串(str)
字节(bytes)
序列支持如下通用操作:
- 索引 通过索引可以访问序列中指定位置的元素(单个)。
- 切片 通过切片可以访问序列中一个区间的元素(多个)。
- 迭代 序列作为可迭代对象,因此,可以通过for循环进行遍历。
- 长度 可以通过len函数获取序列的长度(序列中含有元素的个数)。
- 运算 序列支持+,*,in,not in,比较,布尔运算符。
序列相关函数:
Python
也提供了相应的函数,能够用于根据已经存在的可迭代对象(序列类型都是可迭代类型)创建新的序列类型,实际上,从另一个角度来讲,也就相当于进行了类型转换。
list() / list(iterable)
tuple() / tuple(iterable)
str() / str(object)
bytes() / bytes(str)
len(obj)
max(iterable, *[, default=obj, key=func]) / max(arg1, arg2, *args, *[, key=func])
min(iterable, *[, default=obj, key=func]) / min(arg1, arg2, *args, *[, key=func])
sum(iterable, start=0)
sorted(iterable, key=None, reverse=False)