数据结构
内置序列类型
Python 标准库用 C 实现了丰富的序列类型,列举如下。 容器序列 list、tuple 和 collections.deque 这些序列能存放不同类型的数据 扁平序列 str、bytes 和 array.array,这类序列只能容纳一种类型
容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是引用。
换句话说,扁平序列其实是一段连续的内存空间。
由此可见扁平序列其实更加紧凑,但是它里面只能存放诸如字符、字节和数值这种基础类型。
序列类型还能按照能否被修改来分类。
可变序列 list、bytearray、array.array、collections.deque 和 memoryview。 不可变序列 tuple、str 和 bytes
列表推导和生成器表达式
列表推导是构建列表(list)的快捷方式,
而生成器表达式则可以用来创建其他*任何类型*的序列
res = [ (i, j, k) for i in range(10) # 也可以只有一层循环, 对应好参数 for j in range(10, 20) for k in range(20, 30) ] res = ( (i, j, k) for i in range(10) # 也可以只有一层循环, 对应好参数 for j in range(10, 20) for k in range(20, 30) )
filter 和 map 合起来能做的事情,列表推导也可以做,而且还不需要借助难以理解和阅 读的 lambda 表达式。
symbols = '$¢£¥€¤' beyond_ascii = [ord(s) for s in symbols if ord(s) > 127] # beyond_ascii # == [162, 163, 165, 8364, 164] beyond_ascii_ = list(filter(lambda c: c > 127, map(ord, symbols))) # beyond_ascii_ # == [162, 163, 165, 8364, 164]
虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。
这是因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的
列表,然后再把这个列表传递到某个构造函数里。前面那种方式显然能够节省内存。
# 如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来。 def xxx(x): for i in x: print(i) xxx(i for i in range(10))
元组
有名元组:
collections.namedtuple 是一个工厂函数,它可以用来构建一个带字段名的元组和
一个有名字的类——这个带名字的类对调试程序有很大帮助
from collections import namedtuple City = namedtuple("City", "name position aaa bbb xxx") city = City("tokyo", 1, 2, 3, 4) print(city) print(city.name)
元组拆包:
元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,
外加这个字段的位置。正是这个位置信息给数据赋予了意义。
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
for i in traveler_ids:
print('%s-%s' % i)
用*接收不定长元素
a, *b = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
print(a)
print(b)
切片:
在 Python 里,像列表(list)、元组(tuple)和字符串(str)这类序列类型都支持切 片操作,但是实际上切片操作比人们所想象的要强大很多。
在切片和区间操作里不包含区间范围的最后一个元素是 Python 的风格,这个习惯符合 Python、C 和其他语言里以 0 作为起始下标的传统。
当只有最后一个位置信息时,我们也可以快速看出切片和区间里有几个元 素:range(3) 和 my_list[:3] 都返回 3 个元素。 当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,用后一个数减去 第一个下标(stop - start)即可。 这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两部分,只要写成 my_list[:x] 和 my_list[x:] 就可以了。
>>> lst = list(range(10)) >>> lst [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] SyntaxError: invalid syntax >>> lst[1:3] = [11, 33] >>> lst [0, 11, 33, 3, 4, 5, 6, 7, 8, 9] >>> lst[3:5] = [3355] >>> lst [0, 11, 33, 3355, 5, 6, 7, 8, 9] >>> del lst[5:] >>> lst [0, 11, 33, 3355, 5]
数组
如果我们需要一个只包含数字的列表,那么 array.array 比 list 更高效。数组支持所 有跟可变序列有关的操作,
包括 .pop、.insert 和 .extend。另外,数组还提供从文件读取和存入文件的更快的方法,如 .frombytes 和 .tofile。
from array import array from random import random floats = array('d', (random() for i in range(10**7))) # 利用一个可迭代对象来建立一个双精度浮点数组(类型码是 'd'),这里我们用的可 迭代对象是一个生成器表达式 floats[-1] 0.5749946228902657 fp = open('floats.bin', 'wb') floats.tofile(fp) fp.close() floats2 = array('d') # 新建一个双精度浮点空数组 fp = open('floats.bin', 'rb') floats2.fromfile(fp, 10**7) fp.close() floats2[-1] 0.5749946228902657
另外一个快速序列化数字类型的方法是使用pickle,pickle.dump 处理浮点 数组的速度几乎跟 array.tofile 一样快。不过前者可以处理几乎所有的内置数字 类型,包含复数、嵌套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂 的实现。
从 Python 3.4 开始,数组类型不再支持诸如 list.sort() 这种就地排序方法。要给 数组排序的话,得用 sorted 函数新建一个数组。
参考《流畅的python》