Python中的序列
序列是指一块可以存放多个值的
连续
的内存空间,这些值按一定的顺序进行排列,可以通过每个值的编号也称索引
访问到它们。
文章介绍
- 第一节主要描述了对序列根据不同的情况作出的不同分类
- 第二节主要描述了列表推导式的使用(作用域问题)和生成器表达式的使用
- 第三节主要描述了元组发生改变时的一些情况
- 第四节通过表格的方式划分了列表和元组的一些常见方法
- 第五节描述了拆包操作(*号的作用和*号在函数传参时的作用)
- 第六节描述了切片的作用(切片对象和通过切片改变序列)
一、序列的分类
1. 根据存储对象的类型进行划分
可以分为容器序列和扁平序列
容器序列
容器序列可以存放不同类型的项,存放的是所包含对象的引用,对象可以是任何类型 常见的容器序列有: list和tuple。
# 容器序列存放的是对象的引用,也可以理解为存放的是对象在内存中的地址
li = [7, 8, 9]
lis = [li]
print("当前li对象:", id(li))
print(lis)
li = [9, 10, 11] # 当我们给变量重新赋值时,实际上,变量只是改变了对某个对象的绑定
print("重新绑定后的li对象:", id(li))
print(lis) # 此时,由于原始对象没有发生改变,所以列表中的数据并不会发生改变
print("lis中存储的li对象:", id(lis[0]))
li = lis[0]
print("li对象:", id(li))
print(li)
li[0] = 100
print(lis)
扁平序列
扁平序列在自己的内存空间中存储所包含内容的值。结构更加紧凑,也更加节省内存,但是只能存储同种类型的数据。 常见的扁平序列如: str、array.array
2. 根据序列是否可变(序列内的某个元素发生改变是否会产生新的对象)
不可变序列
序列内的某个元素发生改变时,会产生一个新的对象。 常见的如: tuple、str、 bytes
可变序列
序列内的某个元素发生改变时,操作的还是原有的对象。 常见的如: list、array.array
二、列表推导式和生成器表达式
列表推导式
作用就是可以以更简洁的方式生成一个列表。同时,在作用域方面也有一定的优势
# 使用列表推导式生成列表
L1 = []
for i in range(10, 20):
L1.append(i)
print("L1:", L1)
L2 = [x for x in range(10, 20)]
print("L2:",L2)
可以看到,使用for循环需要三行的代码,使用列表推导式一行就可以解决,代码更加简洁。列表推导式还可以作用于多个可迭代序列的生成。
# 多个可迭代序列生成
L = [5, 6]
L1 = [7, 8]
L2 = [9, 10]
L3 = []
for i in L:
for j in L1:
for k in L2:
L3.append([i, j, k])
print("L3:", L3)
L4 = [[i, j, k] for i in L for j in L1 for k in L2]
print("L4:", L4)
在遍历多个可迭代序列时,列表推导式显得更加简洁。
# 列表推导式在作用域方面的好处
x = 10
L1 = []
for x in range(10, 20):
L1.append(x)
print("L1:", L1)
print("x:", x)
y = 10
L2 = [y for y in range(10, 20)]
print("L2:", L2)
print("y:", y)
可以看到,当我们在使用for循环生成列表时,如果变量使用不规范,很容易导致某些全局变量被改变,但是使用列表推导式则完全不需要考虑这个问题
生成器表达式
与列表推导式的使用方式一致,只是将[]换成(),但是在功能上而言,生成器表达式更加节省内存空间,因为它可以通过迭代器协议逐个产出元素,而不是像列表推导式那样,直接在内存空间中生成所有的元素
三、当元组发生改变时
元组是不可变对象,且相较于列表更加节省内存空间
T1 = (5, 6, 7, 8, 9)
print(id(T1))
T1 = (9, 10)
print(id(T1))
这种方式虽然让T1对应的元组对象发生了改变,但是实际上是改变了T1所绑定的对象,原始的元组对象并没有发生改变。但是有一种特殊情况,确实会出现元组内的元素发生了改变,但是元组对象仍然正常的。
当元组中包含可变对象时
lis = [9, 10, 11]
T1 = (5, 6, 7, 8, lis)
print("T1:", T1)
print("T1对象:", id(T1))
lis.append(12)
print("T1:", T1)
print("T1对象:", id(T1))
正如在前边所说的那样,容器序列保存的是对对象的引用,所以元组本身是不可变的,但是当元组内存存在可变对象的引用时,如果可变对象发生了改变,元组内部对应的元素也会发生改变。
四、列表和元组的一些常用方法
方法 | 列表中是否存在 | 元组中是否存在 | 描述 |
---|---|---|---|
s.__add__(s1) | ✔ | ✔ | s+s1:拼接 |
s.__iadd__(s1) | ✔ | s+=s1:就地拼接 | |
s.append(e) | ✔ | 在最后一个元素后面追加一个元素 | |
s.clear() | ✔ | 删除所有项 | |
s.__contains__(e) | ✔ | ✔ | e in s: 判断某个元素是否在序列中 |
s.copy() | ✔ | 浅拷贝序列 | |
s.count(e) | ✔ | ✔ | 计算元素出现的次数 |
s.__delitem__ | ✔ | 删除位置p上的项 | |
s.extend(it) | ✔ | 追加可迭代对象it中的项 | |
s.__getitem__(p) | ✔ | ✔ | s[p]:获取指定位置上的项 |
s.__getnewargs__ | ✔ | 支持使用pickle优化序列项 | |
s.index(e) | ✔ | ✔ | 找出e首次出现的位置 |
s.insert(p, e) | ✔ | 在位置p上的项之前插入元素e | |
s.__iter__() | ✔ | ✔ | 获取迭代器 |
s.__len__() | ✔ | ✔ | len(s):获取序列中的项数 |
s.__mul__(n) | ✔ | ✔ | s*n:重复拼接 |
s.__imul__(n) | ✔ | s*=n: 就地重复拼接 | |
s.__rmul__ | ✔ | ✔ | n*s: 反向重复拼接 |
s.pop([p]) | ✔ | 移除并返回最后一项或可选位置p上的项 | |
s.remove(e) | ✔ | 把e的值从首次出现的位置上移除 | |
s.reverse() | ✔ | 就地翻转项的顺序 | |
s.__reversed__() | ✔ | 获取从后往前遍历项的迭代器 | |
s.__setitem__(p, e) | ✔ | s[p]=e:把e放在位置p上,覆盖现有的项 | |
s.sort([key], [reverse]) | ✔ | 就地对项排序,key和reverse是可选的关键字参数 |
五、序列和可迭代对象拆包
并行赋值
一个常见的拆包方式,就是将可迭代序列中的元素赋值到对应的变量上
# 并行赋值的要求就是变量的数量要跟序列中的元素数量一致
lis = [5, 6, 7]
a, b, c = lis
print("a:", a)
print("b:", b)
print("c:", c)
使用*获取余下的项
前边的并行赋值,要求变量数量跟序列中的元素数量保持一致,但是有的时候,我们并不能准确的知道序列的具体数量,我们知道要去序列中的第几个元素,这个时候就要用到*来进行捕获多余的变量
lis = [x for x in range(1, 100)]
a, *_, c = lis # 去开头和结尾的两项
print("a:", a)
print("c:", c)
print("_:", _) # 一般情况下,_代表的是临时变量,也就是有些内容后面并不会用到,但是想不出好的承载这个数据的变量名,就用_
*可以获取在拆包过程中多余的内容,拆包的具体分配过程是:
先看*前面有几个变量,则将开头的几个元素给到这几个变量上。 在看后面有几个变量,则将结尾的几个元素一一给到这几个变量上。 然后再将剩下的所有元素,给到*对应的变量上
函数调用中的*
def fun(a, b, c, d, *rest):
print("a:", a)
print("b:", b)
print("c:", c)
print("d:", d)
print("rest:",rest)
fun(*[1, 2], 3, *range(4,7))
在这段代码中,多次使用了*进行了拆包处理,具体的执行流程如下:
- 先将列表[1,2]进行拆包为1和2
- 在将*rage(4, 7)拆包为4, 5, 6
- 找个时候传入的参数元组为(1, 2, 3, 4, 5, 6)
- 然后1, 2, 3, 4分别对应到a, b, c, d这四个参数上,剩余的内容对应到rest参数上
# output
a: 1
b: 2
c: 3
d: 4
rest: (5, 6)
嵌套拆包
和前面的拆包方式一样,元素一一对应,或者用*匹配多余的元素
# 一个简单的嵌套拆包
lis = [5, 6, (7, 8), 9, [10, 11]]
a, b ,*_, (c, d) = lis
print("a:", a)
print("b:", b)
print("c:", c)
print("d:", d)
print("_:", _)
六、切片
切片对象
r = slice(0, -1, 2) # 从可迭代序列的低一个元素开始,到最后一个元素终止,步长为2
L1 = [x for x in range(101)]
print(L1[r])
T1 = [x * 100 for x in range(10)]
print(T1[r])
这里的slice代表的就是创建一个切片对象,实际上,在执行切片操作时,Python也是会产生一个相应的切片对象,在根据这个切片对象去操作相关的序列。
为切片赋值
l = [x for x in range(10)]
print(l)
del l[0:2] # 通过切片删除序列中的某些元素
print(l)
l[0:2] = [5] # 通过切片修改序列中的某些元素
print(l)
# l[0:2] = 3 # 这种方式是错误的,通过切片赋值的内容必须是可迭代对象
print(l)
转载自:https://juejin.cn/post/7393533304504664115