一、数据结构
第一章 序列构成的数组
1.1 内置序列类型概述
容器序列:list、tuple和collections.deque这些序列能存放不同类型的数据。
扁平序列:str、bytes、bytearray、memoryview和array.array,这些序列只能容纳一种类型。
可变序列:list、bytearray、array.array、collections.deque和memoryview。
不可变序列:tuple、str和bytes。
1.2 列表推导和生成器表达式
1.2.1 列表推导和可读性
2-1 把字符串变成Unicode码位的列表
# 方法一
sysmbols = '$¥&*#@!'
codes = []
for symbol in sysmbols:
codes.append(ord(symbol))
print(codes)
# 方法二
sysmbols = '$¥&*#@!'
codes = [ord(symbol) for symbol in sysmbols]
print(codes)
# 结果
[36, 65509, 38, 42, 35, 64, 33]
[36, 65509, 38, 42, 35, 64, 33]
句法提示:python会忽略代码里[]、{}、()中的换行,因此如果你的代码里有多行的列表、列表推导、生成器表达式、字典这一类的,可以省略不太好看的续行符\。
2-2 列表推导不会再有变量泄露的问题
# 列表推导、生成器表达式、集合推导、字典推导,在python3中有自己局部作用域,不会影响同名变量。
x = 'ABC'
dummy = [ord(x) for x in x]
print(x)
print(dummy)
# 结果
ABC
[65, 66, 67]
1.2.2 列表推导同filter和map的比较
2-3 列表推导和map/filter组合结果相同,明显列表推导更简单
sysmbols = '$¥&*#@!'
beyond_ascii = [ord(s) for s in sysmbols if ord(s) > 127]
print(beyond_ascii)
beyond_ascii2 = list(filter(lambda c: c > 127, map(ord, sysmbols)))
print(beyond_ascii2)
# 结果
[65509]
[65509]
1.2.3 笛卡儿积
2-4 使用列表推导计算笛卡儿积
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for size in sizes
for color in colors]
print(tshirts)
# 结果
[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), ('black', 'L'), ('white', 'L')]
列表推导的作用只有一个:生成列表
1.2.4 生成器表达式
2-5 用生成器表达式初始化元组和数组
sysmbols = '$¥&*#@!'
tup = tuple(ord(symbol) for symbol in sysmbols)
print(tup)
import array
arr = array.array('I', (ord(symbol) for symbol in sysmbols))
print(arr)
# 结果
(36, 65509, 38, 42, 35, 64, 33)
array('I', [36, 65509, 38, 42, 35, 64, 33])
2-6 使用生成器表达式计算笛卡儿积
生成器表达式逐个产出元素,不会一次性产出所有列表,避免额外的内存占用。
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
for tshirt in ('%s, %s,' %(c, s) for c in colors for s in sizes):
print(tshirt)
# 结果
black, S,
black, M,
black, L,
white, S,
white, M,
white, L,
1.3 元组不仅仅是不可变的列表
1.3.1 元组和记录
2-7 把元组用作记录
lax_coordinates = (33.9425, -118.408056)
city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8041)
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')]
for passport in sorted(traveler_ids): #迭代过程中,passport变量被绑定到每个元组上
print('%s/%s' %passport)
for country, _ in traveler_ids: #拆包(unpacking),元组中第二个元组没用,用占位符“_”
print(country)
# 结果
BRA/CE342567
ESP/XDA205856
USA/31195855
USA
BRA
ESP
1.3.2 元组拆包
lax_coordinates = (33.9425, -118.408056)
latitude, longitude = lax_coordinates # 元组拆包
print(latitude, longitude)
dm = divmod(20, 8)
print(dm)
t = (20, 8)
dm2 = divmod(*t) # 用*运算符把一个可迭代对象拆开作为函数的参数
print(dm2)
quotient, remainder = divmod(*t)
print(quotient, remainder)
# 结果
33.9425 -118.408056
(2, 4)
(2, 4)
2 4
os.path.split()函数会返回以路径和最后一个文件名组成的元组(path, last_part)
import os
path, filename = os.path.split('D:\pythonProject\Fluent_Python\Data_Structure\\array\\tuple.py')
print(path)
print(filename)
# 结果
D:\pythonProject\Fluent_Python\Data_Structure\array
tuple.py
用 * 来处理剩下的元素
a, b, *rest = range(5)
print(a, b, rest)
a, b, *rest = range(3)
print(a, b, rest)
a, b, *rest = range(2)
print(a, b, rest)
a, *body, c, d = range(5) # 在平行赋值中,*前缀只能用在一个变量名前,但是这个变量可以出现在复制表达式的任意位置
print(a, body, c, d)
*head, b, c, d = range(5)
print(head, b, c, d)
# 结果
0 1 [2, 3, 4]
0 1 [2]
0 1 []
0 [1, 2] 3 4
[0, 1] 2 3 4
1.3.3 嵌套元组拆包
2-8 用嵌套元组来获取经度
metro_areas = [
('Tokyo', 'JP', 36.933, (350689722, 139.691667)), # 每个元组有4个元素,其中最后一个元素时一对坐标
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
print('{:15} | {:^9} | {:^9}'.format('city', 'lat.', 'long.'))
fmt = '{:15} | {:9.4f} | {:9.4f}'
for name, cc, pop, (latitude, longitude) in metro_areas: # 把最后一个元素拆包到由变量构成的元组里,获取坐标
if longitude <= 0: # 输出限制在西半球
print(fmt.format(name, latitude, longitude))
# 结果
city | lat. | long.
Mexico City | 19.4333 | -99.1333
New York-Newark | 40.8086 | -74.0204
Sao Paulo | -23.5478 | -46.6358
1.3.4 具名元组
2-9 定义和使用具名元组
from collections import namedtuple
City = namedtuple('City', 'name country population coordinates') # 创建一个具名元组需要两个参数,一个类名,一个类的各个字段的名字
tokyo = City('Tokyo', 'JP', 36.933, coordinates=(35.689722, 139.691667))
print(tokyo.population)
print(tokyo.coordinates)
print(tokyo[1])
#结果
36.933
(35.689722, 139.691667)
JP
2-10 具名元组的属性和方法(接续2-9)
print(City._fields) # _fields属性时一个包含这个类所有字段名称的元组
Latlong = namedtuple('Latlong', 'lat long')
delhi_data = ('Delhi NCR', 'IN', 21.935, Latlong(28.613889, 77.208889))
delhi = City._make(delhi_data) # 用_make()通过接受一个可迭代对象来生成这个类的一个实例,它的作用跟City(*delhi_data)一样
print(delhi._asdict()) # _asdict()把具名元组以collections.OrderedDict的形式返回,可以用它把元组里的信息友好的呈现
for key, value in delhi._asdict().items():
print(key + ':', value)
#结果
('name', 'country', 'population', 'coordinates')
{'name': 'Delhi NCR', 'country': 'IN', 'population': 21.935, 'coordinates': Latlong(lat=28.613889, long=77.208889)}
name: Delhi NCR
country: IN
population: 21.935
coordinates: Latlong(lat=28.613889, long=77.208889)
1.3.5 作为不可变列表的元组
列表 | 元组 | ||
s._add_(s2) | * | * | s + s2,拼接 |
s._iadd_(s2) | * | s += s2,就地拼接 | |
s.append(e) | * | 在尾部添加一个新元素 | |
s.clear() | * | 删除所有元素 | |
s._contains_(e) | * | * | s是否包含e |
s.copy() | * | 列表的浅复制 | |
s.count(e) | * | * | e在s中出现的次数 |
s._delitem(p) | * | 把位于p的元素删除 | |
s.extend(it) | * | 把可迭代对象it追加给s | |
s._getitem_(p) | * | * | s[p],获取位置p的元素 |
s._getnewargs_() | * | 在pickle中支持更加优化的序列化 | |
s.index(e) | * | * | 在s中找到元素e第一次出现的位置 |
s.insert(p,e) | * | 在位置p之前插入元素e | |
s._iter_() | * | * | 获取s的迭代器 |
s._len_() | * | * | len(s),元素的数量 |
s._mul_(n) | * | * | s * n,n个s的重复拼接 |
s._imul_(n) | * | s *= n,就地重复拼接 | |
s._rmul_(n) | * | * | n * s,反向拼接 |
s.pop([p]) | * | 删除最后或者是位于p的元素,并返回它的值 | |
s.remove(e) | * | 删除s中的第一次出现的e | |
s.reverse() | * | 就地把s的元素倒序排列 | |
s._reversed_() | * | 返回s的倒序迭代器 | |
s._setitem_(p, e) | * | s[p] = e,把元素e放在位置p,替代已经在那个位置的元素 | |
s.sort([key], [reverse]) | * | 就地对s中的元素进行排序,可选的参数有键(key)和是否倒序 |
1.4 切片
1.5 对序列使用 + 和 *
建立由列表组成的列表
2-12 一个包含3个列表的列表,嵌套的3个列表各自有3个元素来代表井字游戏的一行方块。
board = [['_'] * 3 for i in range(3)] #建立一个包含3个列表的列表,被包含的3个列表各自有3个元素
print(board)
board[1][2] = 'X' # 把第1行第2列的元素标记为X
print(board)
# 结果
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
2-13 含有3个指向同一对象的引用的列表是毫无用处的
weird_board = [['_'] * 3] * 3 # 外面的列表其实包含3个指向同一个列表的引用,当不做修改的时候,看起来都还好
print(weird_board)
weird_board[1][2] = '0' # 试图标记第1行第2列的元素,就立马暴露列表内的3个引用指向同一个对象
print(weird_board)
# 结果
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
[['_', '_', '0'], ['_', '_', '0'], ['_', '_', '0']]
1.6 序列的增量赋值
例 *= 在可变和不可变序列上的作用
l = [1, 2, 3]
print(id(l)) # 列表最开始的id
l *= 2
print(id(l)) # 运用增量乘法后,列表的id没变,新元素追加到列表上
t = (1, 2, 3)
print(id(t)) # 元组最开始的id
t *= 2
print(id(t)) # 运用增量乘法后,新的元组被创建
# 结果
2744821281408
2744821281408
2744824891520
2744821338496
一个关于+=的谜题
注意:不要把可变对象放在元组里面。
2-14 一个谜题
>>> t = (1,2,[30,40])
>>> t[2]+=[50,60] # 因为tuple不支持对它的元素赋值,所以会抛出TypeError异常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60]) # t[2]被改动了
1.7 list.sort方法和内置函数sorted
例 list.sort和sorted和它们的关键字参数
fruits = ['grape', 'raspberry', 'apple', 'banana']
print(sorted(fruits)) # 新建一个按照字母排序的字符串列表
print(fruits) # 原列表没有变化
print(sorted(fruits, reverse=True)) # 按照字母降序排列
print(sorted(fruits, key=len)) # 新建一个按照长度排序的字符串列表
print(sorted(fruits, key=len, reverse=True)) # 按照长度降序排列的结果
print(fruits) # 原列表没有变化
fruits.sort() # 对原列表就地排序,返回值None被控制台忽略
print(fruits) # fruits本身被排序
# 结果
['apple', 'banana', 'grape', 'raspberry']
['grape', 'raspberry', 'apple', 'banana']
['raspberry', 'grape', 'banana', 'apple']
['grape', 'apple', 'banana', 'raspberry']
['raspberry', 'banana', 'grape', 'apple']
['grape', 'raspberry', 'apple', 'banana']
['apple', 'banana', 'grape', 'raspberry']
1.8 用bisect来管理已排序的序列
1.8.1 用bisect来搜索
bisect(haystack,needle)在haystack(干草堆)里搜索needle(针)的位置,需要满足的条件是,把needle插入这个位置后,haystack还能保持升序。也就是说这个函数返回的位置前面的值,都小于或等于needle的值。其中haystack必须是一个有序的序列。
2.17 在有序序列中用bisect查找某个元素的插入位置
import bisect
import sys
HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]
ROW_FMT = '{0:2d} @ {1:2d} {2}{0:<2d}'
def demo(bisect_fn):
for needle in reversed(NEEDLES):
position = bisect_fn(HAYSTACK, needle) # 用特定的bisect函数来计算元素应该出现的位置
offset = position * ' |' # 利用该位置来计算出需要几个分隔符号
print(ROW_FMT.format(needle, position, offset)) # 把元素和其应该出现的位置打印出来
if __name__ == '__main__':
if sys.argv[-1] == 'left': #根据执行命令最后一个参数来选用bisect函数,例:python bisect_demo.python left
bisect_fn = bisect.bisect_left
else:
bisect_fn = bisect.bisect
print('DEMO:', bisect_fn.__name__) # 把选定的函数打印出来
print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK))
demo(bisect_fn)
# 结果
DEMO: bisect_right
haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
31 @ 14 | | | | | | | | | | | | | |31
30 @ 14 | | | | | | | | | | | | | |30
29 @ 13 | | | | | | | | | | | | |29
23 @ 11 | | | | | | | | | | |23
22 @ 9 | | | | | | | | |22
10 @ 5 | | | | |10
8 @ 5 | | | | |8
5 @ 3 | | |5
2 @ 1 |2
1 @ 1 |1
0 @ 0 0
# 执行python bisect_demo.python left结果
DEMO: bisect_left
haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
31 @ 14 | | | | | | | | | | | | | |31
30 @ 13 | | | | | | | | | | | | |30
29 @ 12 | | | | | | | | | | | |29
23 @ 9 | | | | | | | | |23
22 @ 9 | | | | | | | | |22
10 @ 5 | | | | |10
8 @ 4 | | | |8
5 @ 2 | |5
2 @ 1 |2
1 @ 0 1
0 @ 0 0
2-18 根据一个分数,找到它所对应的成绩
import bisect
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
i = bisect.bisect(breakpoints, score)
print(grades[i])
if __name__ == '__main__':
[grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
# 结果
F
A
C
C
B
A
A
2.8.2 用bisect.insort插入新元素
insort(seq, item)把变量item插入到序列seq中,并能保持seq的升序顺序。
2-19 insort可以保持有序序列的顺序
import bisect
import random
SIZE = 7
random.seed(1729)
my_list = []
for i in range(SIZE):
new_item = random.randrange(SIZE * 2)
bisect.insort(my_list, new_item)
print('%2d ->' % new_item, my_list)
# 结果
10 -> [10]
0 -> [0, 10]
6 -> [0, 6, 10]
8 -> [0, 6, 8, 10]
7 -> [0, 6, 7, 8, 10]
2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]
1.9 当列表不是首选时
1.9.1 数组
2-20 一个浮点型数组的创建、存入文件和从文件读取的过程
from array import array
from random import random
floats = array('d', (random() for i in range(10 ** 7))) # 利用一个可迭代对象生成器表达式来建立一个双精度浮点数组(类型码是’d')
print(floats[-1]) # 查看数组的最后一个元素
fp = open('floats.bin', 'wb')
floats.tofile(fp) # 把数组存入一个二进制文件里
fp.close()
floats2 = array('d') # 新建一个双精度浮点空数组
fp = open('floats.bin', 'rb')
floats2.fromfile(fp, 10 ** 7) # 把1000万个浮点数从二进制文件里读取出来
fp.close()
print(floats2[-1]) # 查看新数组的最后一个元素
print(floats2 == floats) # 检查两个数组的内容是不是完全一样
# 结果
0.5099803693183845
0.5099803693183845
True
列表 | 数组 | ||
s.__add__(s2) | * | * | s + s2,拼接 |
s.__iadd__(s2) | * | * | s += s2,就地拼接 |
s.append(e) | * | * | 在尾部添加一个元素 |
s.byteswap | * | 翻转数组内每个元素的字节序列,转换字节序 | |
s.clear() | * | 删除所有元素 | |
s.__contains__(e) | * | * | s是否含有e |
s.copy() | * | 对列表浅复制 | |
s.__copy() | * | 对copy.copy的支持 | |
s.count(e) | * | * | s中e出现的次数 |
s.__deepcopy__() | * | 对copy.deepcopy的支持 | |
s.__delitem__(p) | * | * | 删除位置p的元素 |
s.extend(it) | * | * | 将可迭代对象it里的元素添加到尾部 |
s.frombytes(b) | * | 将压缩成机器值得字节序列读出来添加到尾部 | |
s.fromfile(f,n) | * | 将二进制文件f内含有机器值读出来添加到尾部,最多添加n项 | |
s.fromlist(l) | * | 将列表里的元素添加到尾部,如果其中任何一个元素导致了TypeError异常,那么所有的添加都会取消 | |
s.__getitem__(p) | * | * | s[p],读取位置p得元素 |
s.index(e) | * | * | 找到e在序列中第一次出现的位置 |
s.insert(p,e) | * | * | 在位于p得元素之前插入元素e |
s.itemsize | * | 数组中每个元素得长度是几个字节 | |
s.__iter__() | * | * | 返回迭代器 |
s.__len__() | * | * | len(s),序列的长度 |
s.__mul__(n) | * | * | s * n,重复拼接 |
s.__imul__(n) | * | * | s *= n,就地重复拼接 |
s.__rmul__(n) | * | * | n * s,反向重复拼接* |
s.pop([p]) | * | * | 删除位于p得值并返回这个值,p得默认值是一个元素的位置 |
s.remove(e) | * | * | 删除序列里第一次出现的e元素 |
s.reverse(e) | * | * | 就地调转序列中元素的位置 |
s.__reverse__() | * | 返回一个从尾部开始扫描元素的迭代器 | |
s.__setitem__(p,e) | * | * | s[p] = e,把位于p位置的元素替换成e |
s.sort([key], [reverse]) | * | 就地排序序列,可选参数有key和reverse | |
s.tobytes() | * | 把所有元素的机器值用bytes对象的形式返回 | |
s.tofile(f) | * | 把所有元素以机器值得形式写入一个文件 | |
s.tolist() | * | 把数组转换成列表,列表里的元素类型是数字对象 | |
s.typecode | * | 返回只有一个字符的字符串,代表数组元素在C语言中得类型 |
1.9.2 内存视图
2-21 通过改变数组中的一个字节来更新数组里某个元素的值。
import array
numbers = array.array('h', [-2, -1, 0, 1, 2]) # 利用含有5个短整型有符号整数的数组创建一个memoryview
memv = memoryview(numbers)
print(len(memv))
print(memv[0]) # memv里的5个元素跟数组里的没有区别
memv_oct = memv.cast('B') # 创建一个memv_oct,把memv里的内容转换成‘B’类型,无符号字符
print(memv_oct.tolist()) # 以列表得形式查看memv_oct的内容,出现10个元素,可以研究存储知识,源码,补码
memv_oct[5] = 4 # 把位于位置5的字节赋值成4
print(numbers) # 因为把占2个字节的整数的高位字节改成4,所以这个有符号整数的值就变成了1024
# 结果
5
-2
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
array('h', [-2, -1, 1024, 1, 2])
1.9.3 NumPy和SciPy
2-22 对numpy.ndarray的行和列进行基本操作
import numpy
a = numpy.arange(12) # 新建一个0~11的整数numpy.ndarray
print(a)
print(type(a))
print(a.shape) # 数组的维度,一个一维的,有12个元素
a.shape = 3, 4 # 把数组变成2维的
print(a)
print("------------")
print(a[2]) # 打印出第2行
print("----------")
print(a[2, 1]) # 打印第2行第1列的元素
print("----------")
print(a[:, 1]) # 打印第1列
print("----------")
print(a.transpose()) # 把行和列交换,得到一个新数组
# 结果
[ 0 1 2 3 4 5 6 7 8 9 10 11]
<class 'numpy.ndarray'>
(12,)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
------------
[ 8 9 10 11]
----------
9
----------
[1 5 9]
----------
[[ 0 4 8]
[ 1 5 9]
[ 2 6 10]
[ 3 7 11]]
1.9.4 双向队列和其他形式的队列
from collections import deque
dq = deque(range(10), maxlen=10) # maxlen是一个可选参数,代表这个队列可以容纳的元素的数量,一旦设定,这个属性就不能修改了
print(dq)
dq.rotate(3) # 队列的旋转操作接受一个n,当n>0时,队列的最右边的n个元素会被移动到队列的左边。当n<0时,最左边的n个元素会被移动到右边
print(dq)
dq.rotate(-4)
print(dq)
dq.appendleft(-1) # 当试图会一个已满的队列做头部添加操作的时候,它尾部的元素会被删除掉。注意在下一行里,元素0被删除了
print(dq)
dq.extend([11, 22, 33]) # 在尾部添加3个元素的操作会挤掉-1、1、2
print(dq)
dq.extendleft([10, 20, 30, 40]) # extendleft(iter)方法会把迭代器里的元素逐个添加到双向队列的左边,因此迭代器里的所有元素会逆序出现在队列里
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([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)