Python中存在大量的序列类型,每种序列类型都具备自己的特点与使用场景,了解其机制有利于构造更加高效的代码与自制序列类。
内置序列概况
按照能否存储同一数据类型数据分为:
- 容器序列:list,tuple,collections.deque,存储的为任意类型的引用,内存空间不连续。
- 扁平序列:str,bytes,bytearray,memoryview,array.array,存储的是同一类型的值,内存空间连续。
上段时间我总结了关于Python中值传递与引用传递问题时,有提到相关概念的区别。
按照序列类型能否修改:
- 可变序列:list,bytearray.array.array,collection.deque,memoryview
- 不可变序列:tuple,str,bytes
list序列
列表类型是Python中常见的序列类型,其使用已烂熟于心,不妨关于列表的创建方法:
列表推导与列表生成
列表推导的简单使用
列表推导就是用于生成列表的,其是一种易于阅读的列表生成方式。
传统的列表构造方式:for + append
1
2
3
4
5
6
7# 将一个字符串转换为Unicode码位列表
symbols = "$#@%&*"
codes = []
for symbol in symbols:
codes.append(ord(symbol))
print(codes)
# [36, 35, 64, 37, 38, 42]列表生成式:[ func(s) for s in strs]
1
2
3
4
5# 将一个字符串转换为Unicode码位列表
symbols = "$#@%&*"
codes = [ord(symbol) for symbol in symbols ]
print(codes)
# [36, 35, 64, 37, 38, 42]
使用标准:
上面两种方式实现了相同的功能,使用哪一种取决于逻辑的复杂程度,逻辑复杂需要编写多行才能表示的使用传统for循环的方式更清晰,逻辑简单的使用列表推导方式则更加易读。
**TIPS:**Python会忽略代码中[],(),{}中的换行符号,方便逻辑展示。
列表推导与map,filter的关系
关系就是:map,filter能做的,利用列表推导同样可以实现。
示例:
1 | # 得到所有ASCII大于36的结果,列表生成 |
1 | # 得到所有ASCII大于36的结果,map,filter,lambda |
列表推导与笛卡儿积
在列表推导中也可以生成笛卡儿积,直接看代码即可;
1 | sizes = ["small" , "medium" , "large"] |
列表生成式
列表生成式也是生成列表的方式,其遵循了迭代器协议,逐个的产出元素,而不是建立完整的列表,显然可以节省内存。其形式与列表推导式大同小异,仅仅将方括号替换为圆括号。
- 生成式是唯一参数时,不需要添加圆括号。
1 | symbols = "$#@%&*" |
- 生成式不是唯一参数时,需要添加圆括号。
1 | import array |
- 生成式计算笛卡儿积
1 | sizes = ["small" , "medium" , "large"] |
Tuple序列
元组不仅仅是不可变的列表,如何使用也取决于如何看代元组,例如将元素视为用于记录的字段数据。那么其数量与位置信息则是非常重要的,
- 元组用于记录:
1 | metro_areas = [ |
- 元组内部可嵌套,其内部嵌套标准这里不讨论。
- 元组可以进行拆包
元组拆包
拆包就是将元组中的各元素一次性提取到多变量中。平行赋值是元组拆包的形式之一,也是最好辨认的形式。
- 平行赋值示例:
1 | lax_coordinates = (33.9425 , -118.4080) |
- “*” 来表示剩余的元素
1 | a , b , *rest = range(5) |
- 嵌套元组的拆包
1 | metro_areas = [ |
具名元组
具名元组是collection类的一个工厂函数,可以用来构建一个带字段名的元组和一个有名字的类。具体使用方式这里不表。
1 | from collections import namedtuple |
序列中的 + 与 *
Python中的序列是支持的+与* 操作的,用于序列的拼接。而用于拼接的序列是不会被更改的,而是创建一个包含有同样类型的序列并返回。
“+”用于拼接序列时,一般要求左右元素的类型一致。
1 | l = [1,2,3] |
注意:
在使用a * n语句时,序列中的a中的元素是对其他可变对象的引用时,得到的结果可能会与预期存在不一致。我自己在编写算法过程中也遇到类似的情形。比如:初始化列表中的列表时,得到的三个元素是三个引用,三个引用指向的地址一致,
示例:
1 | # 正确的使用姿势 |
序列中的 += 与 *= 运算
序列中的增量赋值运算,大同小异,只介绍+= 即可。理解增量赋值运算,主要是其结果到底是就地加法还是构造新的对象并赋值。
Python遇到+=时
- 首先会调用“__iadd__”(就地加法方法),__
- 如果没有实现,则调用“__add__”,此时等价于a = a +b.
而上面是否实现了就地加法,却决于a是否是可变对象,比如列表就实现了。
1 | # 可变类型遇到增量赋值,一般为原地修改 |
list.sort与内置的sorted方法
上面的两个方法作用一致,其差别就是list.sort方法会选择原地重建列表结构,地址不变。内置的sorted方法则是重构一个列表对象,并返回。
奇妙的key参数
在list.sort\sorted\min\max函数中均提供了key参数,用于进行比较大小的依据。在一般的语言中,甚至在python2中也采用了双参数函数用于返回“-1,0,1”,以表示大小。而Python3中的key参数支持单参数函数进行比较。单参数的优势在于其更加高效,每个key函数只会调用一次。在排序中,自然会进行两个键值的比较,但是其发生才C语言一层,这样保证了其高效性。
示例:实现字符与数字的序列排序
1 | l = [24,14,"28" , 5,"9","1","0" , 6,"23" , 19] |
bisect来管理已排序序列
看理解就是利用二分查找,用来快速定位元素。支持左右二分查找,支持插入元素等。这里不一一贴代码。