序列
1.内置序列类型概述
容器序列
容器序列:可以存放不同类型的数据(存放数据的引用(对象))
list、tuple、collections.deque
扁平序列
扁平序列:只能存放同种数据类型,存放的是对象的值,不是引用,扁平序列通常是一段连续的内存空间
str、bytes、bytearray、array.array、memoryview
可变序列
可以进行增删改的序列
list、collections.deque、bytearray、array.array、memoryview
不可变序列
不能被修改
tuple、str、bytes
2.列表推导和生成器表达式
列表推导
如果只是单纯的生成一个列表,那么建议使用了列表推导,而不是for循环
列表推导实例:
my_list1 = [str(i) for i in range(10)]
my_list2 = [i for i in range(10) if i>5]
my_list3 = [(color, size) for color in colors for size in sizes]
- 列表推导适用与代码量不大(不超过一行),且单纯为了生成列表或其他类型序列
- 列表推导中的变量在python3中不存在变量泄露的问题
生成器表达式
生成器表达式是列表推导的进阶版,它背后遵循了迭代器的原则,会逐个生成元素,而不是直接先建立一整张完整的列表,利用这种方式很好的节省了内存空间
意思就是当我需要的时候元素才会生成,而不是一开始就生成所有的元素,这样造成了内存的浪费
生成器表达式和列表推导语法差不多,只是将方括号换成了圆括号
生成器表达式实例:
my_generator = ('%s %s'%(color,size) for color in colors for size in sizes)
生成器表达式,只在每次for循环运行时产生一个元素,如果colors、sizes中有大量的数据,那么生成器可以很好的节省空间
3.元组
元组作为记录使用
说到元组,第一印象就是不可变序列,不能被修改(可以删除整个元组),元素位置不能变,
但是元组除了是不可变序列以外,它还可以用于记录(联想数据库中的记录)
记录的位置是不能变的,也就是说元组的元素位置是有意义的,不能改变
元组实例:
my_tuple = ('yangdaxian', 18, 'man')
上面的示例元组可以理解为数据库中的一张表,表中有3个字段分别为:姓名,年龄,性别,
而这里my_tuple元组就表示表里的一条记录。
所以说,元组可以完美的被当作记录来使用
students = [('yangdaxian', 18, 'man'), ('xiaoluo', 17, 'woman'), ('tingting', '1', 'woman')]
print(sorted(students))
for name, _, _ in students:
print(name)
#输出结果
[('tingting', '1', 'woman'), ('xiaoluo', 17, 'woman'), ('yangdaxian', 18, 'man')]
yangdaxian
xiaoluo
tingting
Process finished with exit code 0
元组拆包
元组拆包可以应用到任何可迭代对象上
实例:
people = ('yangdaxian', 18)
name, age = people
print(name, age)
#输出
yangdaxian 18
a=3
b=4
a,b=b,a #直接交换
print(a,b)
#输出
4 3
def add(a,b):
return a+b
demo_data=(4,5)
print(add(*demo_data))#作为函数的参数,可以用*解包
#输出
9
name, _ = ['yangdaxian',18] #_用作占位符,数组也可以拆包,只要是可迭代对象都行
print(name)
#输出
yangdaxian
用*来处理剩下的元素
a,b,*rest=range(5) #*后面必须接变量名
print(rest)
#输出
[2, 3, 4]
a,b,*rest=range(2)
print(rest)
#输出
[]
嵌套元组拆包
demo=[('yzz',18,('sing', 'play')),('luo',18,('eat','dance')),('ting',3,('milk','sleep'))]
for name,age,(hobby1,hobby2) in demo:#这里遍历数组demo,拆包的时候注意要一一对应
print(hobby1,end=' ')
#输出
sing eat milk
具名元组(带有字段的记录)
collections.namedtuple
前面说到可以将元组看为一个记录,但是记录是有字段的,这个用元组怎么解决呢?
答案就是使用具名元组
- 使用具名元组实例化的对象与元组本身在内存中的大小是一样的
- 使用具名元组实例化的对象比普通对象还要小一些
说白了具名元组可以快速简单的创建一个类
实例:
from collections import namedtuple
#namedtuple的第一个参数是类名,第二个参数是类中的字段名,也就是属性名,可以是一个字符串以空格隔开,也可以是一个可迭代序列,如['name','age','sex','hobbys'],但是一般直接写成'name age sex hobbys'这种方式比较方便
#注意这里的name、age、sex、hobbys仅仅只是名字,当实例化时可以任意指定数据类型
#就像name用的字符串'yangdaxian',age用的整型18,hobbys用的列表
People=namedtuple('People','name age sex hobbys')
p1=People('yangdaxian',18,'man',['programme','playgame'])
p2=People('ting',1,'woman',['eat','sleep'])
print(p1,p2,sep='\n')
print(p1.hobbys) #直接对象.字段名的方式即可获取属性值
#输出
People(name='yangdaxian', age=18, sex='man', hobbys=['programme', 'playgame'])
People(name='ting', age=1, sex='woman', hobbys=['eat', 'sleep'])
['programme', 'playgame']
具名元组的专有属性
#类属性 _fields
#类方法 _make(iterable) 作用:接受一个可迭代对象来生成一个类的实例
#实例方法 _asdict() 作用:将类的实例转换成一个有序字典对象(OrderedDict)
例子:
print(People._fields) #类属性
print('=====================')
demo_01=('tingting',1,'woman',['eat','sleep'])
p3=People._make(demo_01) #类方法
print(p3)
print('===========================')
print(p3._asdict()) #实例方法
#输出
('name', 'age', 'sex', 'hobbys')
=====================
People(name='tingting', age=1, sex='woman', hobbys=['eat', 'sleep'])
===========================
{'name': 'tingting', 'age': 1, 'sex': 'woman', 'hobbys': ['eat', 'sleep']}
元组作为不可变列表使用
元组可以作为不可变列表,除了增删改之外的其他方法,元组和列表基本相同
4.切片
切片为什么会忽略最后一个元素
本质就是为了方便人类理解
reason1:
当只有最后一个位置信息时,我们可以快速看出切出来的序列长度
a=[i for i in range(10)]
b=a[:4] #看到最后一个位置为下标4,那么立即可以得出b的长度为4
print(b)
#输出
[0, 1, 2, 3]
reason2:
当起始、结束位置信息都有时,可以快速看出切片的长度
a=[i for i in range(10)]
b=a[2:5] #直接5-2=3,得出b的长度为3
print(b)
#输出
[2, 3, 4]
reason3:
可以方便的用任意一个下标分隔序列
a=[i for i in range(10)]
b=a[:4]
c=a[4:]
print(b,c,sep='\n')
#输出
[0, 1, 2, 3]
[4, 5, 6, 7, 8, 9]
有间隔的切片
a='mylyovlepoaevute'
print(a[::3]) #从起点开始切,每间隔3个取后面那个元素
#输出
mylove
多维切片
python内置序列类型都是一维的,并不支持多维,但是python扩展库NumPy支持
用法:
import numpy
a = numpy.arange(12)
print(a)
print(a.shape)
a.shape = 3, 4
print(a)
print(a[:, 0]) #[]里面有逗号表示多维切片,这里是二维
#输出
[ 0 1 2 3 4 5 6 7 8 9 10 11]
(12,)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[0 4 8]
…在多维切片中的使用
比如有一个四维数组
a=[i,:,:,:]#可以这么切
a=[i,...]#也可以这么切,注意这里...是3个英文句号,不是省略号
给切片赋值
a = [1, 2, 3, 4, 5, 6]
print(a)
a[2:5] = [100, 200] #给切片赋值,右边必须是一个可迭代序列而不能是一个单一的元素
print(a) #如:a[2:5]=100,会报错
#输出
[1, 2, 3, 4, 5, 6]
[1, 2, 100, 200, 6]#只要右边是一个可迭代序列,那么即使元素比左边的切片元素少,后面少 的元素默认赋值给切片为空
5.对序列使用+和*
+和*两边的序列类型必须一样
使用+和*拼接后会生成一个新的序列,不会影响原序列
a = 'abc' * 2 + 'love'
print(a)
#输出
abcabclove
初始化由列表组成的列表
当使用*初始化由列表组成的列表时,会有个坑需要注意
my_list = [[1, 2]] * 3
print(my_list)
#输出
[[1, 2], [1, 2], [1, 2]]
这里初始化后得到的列表里实际上存放的是3个引用,并且这3个引用都是指向同一个列表
列表的引用和新的列表
- 使用*初始化列表的列表实际上是放的内部列表的引用,如:
#例1
a = [['_'] * 3] * 3
print(a)
a[0][2] = 'X'
print(a)
#输出
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]
上面,给a的第一行第3列元素赋值,发现内部列表中所有对应位置都被赋值了,这个问题就说明了此时的内部列表是对同一个对象的引用(说白了就是那一个列表),所有才会造成这种情况
例1的本质用下面代码可以解释:
a = []
b = ['_']*3
for i in range(3):
a.append(b)
print(a)
a[0][2] = 'X'
print(a)
#输出
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', 'X'], ['_', '_', 'X'], ['_', '_', 'X']]
- 使用列表推导初始化列表的列表,生成的内部列表就不是引用,而是新的列表
#例2
a = [['_'] * 3 for i in range(3)]
print(a)
a[0][2] = 'X'
print(a)
#输出
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', 'X'], ['_', '_', '_'], ['_', '_', '_']]
使用列表推导,每次都会生成一个新的内部列表,不是引用同一个列表
例2原理如下:
a = []
b = ['_']
for i in range(3):
a.append(b * 3)
print(a)
a[0][2] = 'X'
print(a)
#输出
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', 'X'], ['_', '_', '_'], ['_', '_', '_']]
这里每次b*3相当与生成一个新的列表
6.+=和*=
+=和*=都是就地修改原始序列
可变序列2个操作都支持,不可变序列支持*=,但是不支持+=
#可变序列
a = [1, 2]
print(id(a))
a *= 2
print(id(a))
#输出
1649802291776
1649802291776
结果还是原来的对象
#不可变序列
a = (1, 2)
print(id(a))
a *= 2
print(a)
print(id(a))
#输出
1945920343168
(1, 2, 1, 2)
1945920646256
结果是一个新的对象,因为不可变序列是不可以被改变的,所以只能生成一个新的对象
一个经典的例子
>>> a = (1,2,[3,4])
>>> a[2]*=2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> a
(1, 2, [3, 4, 3, 4])
#这里给元组赋值会报错,但是a确实已经被修改了
#原因是因为上面的操作是会先修改列表,这个是被允许的,但是后面一步是给元组赋值,所以会报错,同时a也被修改了
7.list.sort()和sorted()
list.sort()会就地排序,返回None,也就是说不会产生新的序列
sorted()会在原序列的基础上排序,会产生(返回)一个新的序列,它接受任何的可迭代序列,包括不可变序列
不可变序列其实可以排序,只不过原序列还是不变的,但是利用sorted()生成一个新的排好序的序列
因为list.sort()会就地修改原序列,所以不能用在不可变序列,如元组,
但是sorted()会产生一个新的序列,所以可以用于不可变序列进行排序
a = (2, 5, 7, 18, 8)
b = sorted(a)
print(a)
print(b)
#输出
(2, 5, 7, 18, 8) #元组a还是没变
[2, 5, 7, 8, 18]
使用关键字len,根据元素长度排序,如果2个或多个元素长度相同,那么会按照原序列的位置排序
hobbys = ['eat', 'sleep', 'sing', 'dance']
print(sorted(hobbys, key=len))
#输出
['eat', 'sing', 'sleep', 'dance']
8.利用bisect管理有序序列
使用bisect(序列,a),前提是改序列已经排好序,且是升序排列
本质是使用了二分查找法
这个方法的作用是在序列中找出a应该在序列中的位置(且要满足如果插入a到序列,序列还要保持升序),返回一个索引值
import bisect
haystack = [1, 4, 5, 8, 10, 24, 24, 30]
print(bisect.bisect(haystack, 9))#bisect.bisect()的作用就是实现快速查找
#结果
4
bisect.bisect_right() #如果要找的值与序列中某个值相同,那么返回右边的索引,bisect.bisect()默认就是bisect.bisect_right()
bisect.bisect_left() #如果要找的值与序列中某个值相同,那么返回左边的索引
bisect比index快
使用bisect.insort()插入元素
bisect.insort(seq, item),向seq序列中插入元素item,使用该函数在已排序的序列中插入元素后,序列任然保持顺序
import bisect
my_list=[2,5,8,9,13]
bisect.insort(my_list, 6)
print(my_list)
#输出
[2,5,6,8,9,13]
9.数组(取代只包含数字的列表)
如果需要一个只包含数字的列表,那么可以使用数组array.array()
因为数组背后原理是,它的元素不是对象,而是数字的机器翻译,即字节,所以数组的效率比列表高许多
数组还可以直接将数据写入一个文件,也可以从一个二进制文件中读取数据
例子:
from array import array
from random import random
my_array1 = array('d', (random() for i in range(10**7)))#d表示双精度浮点型
print(my_array1[-1])
fp = open('./data.bin', 'wb')#新建文件(这里不用你事先建好该文件)
my_array1.tofile(fp)#写入文件
fp.close()
fp = open('./data.bin', 'rb')
my_array2 = array('d')#建一个空的数组
my_array2.fromfile(fp, 10**7)#从文件读取数据,读取10**7行数据
print(my_array2[-1])
#------------------------------------------------------------------------
a = array('b')#新建一个类型码为b(表示有符号的字符,signed char)的数组a
a.typecode#获取数组a的元素类型码
10.内存视图
memoryview是一个内置类,它能够在不复制内容的情况下操作同一个数组的不同切片,可以实现在数据结构之间的共享内存
numbers = array.array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)#得到一个numbers的内存视图对象
memv_oct = memv.cast('B')#将memv里的内容转换成B类型,即无符号字符
print(memv_oct.tolist())
memv_oct[5] = 4
print(numbers)
#输出
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
array('h', [-2, -1, 1024, 1, 2])#数据改变了,但是numbers的类型没变
11.NumPy和SciPy(工业级数学计算)
NumPy和SciPy都是第三方库,SciPy基于NumPy
例子:
import numpy
a = numpy.arange(12)
print(a)
print(a.shape)
a.shape = 3,4 #将a转换为一个3行4列的二维数组
print(a)
print('=========================')
print(a.transpose()) #行列颠倒,此时数组a并没有变
print('=========================')
print(a)
#输出
[ 0 1 2 3 4 5 6 7 8 9 10 11]
(12,) #一个有12个元素的一维数组
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
=========================
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
=========================
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
NumPy读取和写入数据
import numpy
floats = numpy.loadtxt('xxx.txt')#加载一个数据文本文件的内容到floats
numpy.save('my_numpy', floats)#将floats变量里的数据写入一个my_numpy.npy的文件(这个文件可以不用先建立,会自动生成),这里文件后缀名默认为.npy,可以不写
floats2 = numpy.load('my_numpy.npy', 'r+')#读取my_numpy.npy文件的内容到floats2,这里的load使用了内存映射的技术(不知道是不是'r+'的原因),让我们在内存不足时也能对数组做切片操作
12.队列(取代列表的又一种类型)
from collections import deque
dq = deque(range(10), maxlen=10)#maxlen是一个可选参数,代表这个队列可以容纳的元素的数量,而且一旦设定,这个属性就不能修改了
print(dq)
dq.rotate(3)#队列的旋转操作接受一个参数n,当n>0时,队列的最右边的n个元素会被移动到队列的左边
print(dq)
dq.rotate(-3)#当n<0时,队列的最左边的n个元素会被移动到右边
print(dq)
dq.appendleft(-1)#当给队列的头部添加元素时,尾部的元素会被删除
print(dq)
dq.extend([20, 30, 40])#在尾部添加元素,会挤掉头部的元素
print(dq)
dq.extendleft([10, 11, 12])#在头部添加元素,这里时是把迭代器里的元素逐个添加到头部,所以迭代器的元素会逆序出现在队列里
print(dq)
#输出
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([-1, 0, 1, 2, 3, 4, 5, 6, 7, 8], maxlen=10)
deque([2, 3, 4, 5, 6, 7, 8, 20, 30, 40], maxlen=10)
deque([12, 11, 10, 2, 3, 4, 5, 6, 7, 8], maxlen=10)