第二章 - 序列构成的数组
2.1 内置序列类型概览
Python标准库用C实现了丰富的序列类型;
容器序列
list, tuple, collections.deque这些序列能存放不同类型的数据。容器序列存放的是它们所包含的任意类型的对象的引用。
扁平序列
str, bytes, bytearray, memoryview, array.array 这些序列只能容纳一种类型。扁平序列存放的是值,而不是引用。
可变序列
list, bytearray, array.array, collections.deque, memoryivew.
不可变序列
tuple, str, bytes.
2.2 列表推导和生成器表达式
filter和map合起来能做的事情,列表推导也能做,而且还不需要借助难以理解和阅读的lambda表达式。
def mul(x): return x * x def compare(x): return True if x > 2 else False aa = map(mul, [1,2,3,4,5]) bb = filter(compare, aa) print(list(bb)) >>> [4, 9, 16, 25] cc = [x*x for x in [1,2,3,4,5] if x>2] print(cc) >>> [9, 16, 25]
笛卡尔积(假设集合A={a, b},集合B={0, 1, 2},则两个集合的笛卡尔积为{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}。)
colors = ["black", "white"] sizes = ["S", "M", "L"] t_shirts = [(color, size) for color in colors for size in sizes] print(t_shirts) >>> [('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]
t_shirts = [(color, size) # 自动按照size排序
for size in sizes
for color in colors
]
print(t_shirts)
>>>
[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), ('black', 'L'), ('white', 'L')]
2.3 元组不仅仅是不可变的列表
3大功能:
记录数据
不可变列表
拆包
2.3.1 元组和记录
元组其实是对数据的记录: 元组中的每个元素都存放了记录中一个字段的数据,外加这个字段的位置。正是这个位置信息给数据赋予了意义。
traveler_ids = [("USA", "31195855"), ("BRA", "CE342567"), ("ESP", "XDA205856")] for passport in sorted(traveler_ids): print("%s/%s" % passport) for country, _ in traveler_ids: # _ 占位符(一个位) print(country) for country, *_ in traveler_ids: # *_ 占位符(多个位) print(country)
t = (20, 8)
a, b = divmod(*t) # 元组拆包
print(a, b)
>>>
2 4
def TT(**kwargs):
print(kwargs)
tt = {"a": 11, "b": 33}
TT(**tt) # 字典拆包
>>>
{'a': 11, 'b': 33}
import os
_, filename = os.path.split("/home/cloud/.ssh/idrsa.pub")
print(filename)
>>>
idrsa.pub
a, b, *rest = range(10)
print(a, b, rest)
>>>
0 1 [2, 3, 4, 5, 6, 7, 8, 9]
aa, bb, *rests = range(2)
print(aa, bb, rests)
>>>
0 1 []
*head, aaa, bbb = range(10)
print(head, aaa, bbb)
>>>
[0, 1, 2, 3, 4, 5, 6, 7] 8 9
2.3.4 命名元组
collections.namedtuple 是一个工厂函数(工厂函数:看着像一个函数,实际上是一个类;当调用它是,实际上是生成了该类的一个实例,就像是工厂的流水线;int, dict, tuple, list 都是内置工厂函数), 它可以用来构建一个带字段名的元组和一个有名字的类。
用namedtuple构建的类的实例所消耗的内存跟元组是一个样的,因为字段名都被存放在对应的类里面。
City = namedtuple("City", "name, country, population, coordinates") tokyo = City("Tokyo", "JP", 36.933, (35.689722, 139.691667)) print(tokyo) >>> City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667)) print(City._fields) # 显示这个类所包含的字段名称 >>> ('name', 'country', 'population', 'coordinates') print(tokyo._asdict()) # 以collections.OrderedDict形式返回 >>> OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', 36.933), ('coordinates', (35.689722, 139.691667))])
2.3.5 不可变列表的元组
除了跟增减元素相关的方法之外,元组支持列表的其他所有方法。
2.4 切片
s = "bicycle" print(s[::-1]) # 对字符串反转 >>> elcycib print(s[::-2]) >>> eccb
给切片赋值
l = list(range(10)) print(l) >>> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] l[2: 5] = [20, 30] # 必须是可迭代对象 print(l) >>> [0, 1, 20, 30, 5, 6, 7, 8, 9]
2.5 对序列使用+和*
+和* 都遵循不修改原有的操作对象,而是构建一个全新的序列。
2.6 序列的增量赋值
+=背后的特殊方法是__iadd__方法。
>>> a += b
如果a实现了__iadd__方法,就会调用这个方法,同时对可变序列来说, a就会原地改动,就像调用了 a.extend(b) 一样。但是如果a没有实现__iadd__, a += b这个表达式的效果就变得跟 a = a + b 一样了: 首先会计算 a+b 得到一个新的对象,然后赋值给a。
可变序列一般都实现了__iadd__方法, 因此 +=是就地加法。而不可变序列根本就不支持这个操作,对这个方法的实现也就无从谈起。
注意: str是一个例外,因为Cpython对字符串 +=做了优化,因为字符串拼接太重要了。
*= 对应的是 __imul__方法。
2.7 list.sort方法和内置函数sorted
list.sort方法会就地排序列表,也就是说不会创建一个副本,并且返回值为None。
与list.sort相反的是内置函数sorted, 它会创建一个新的列表作为返回值; 该方法可以接收任何形式的可迭代对象,包括不可变序列或生成器。
list.sort和sorted函数都有两个可选的关键字参数: reverse key
reverse: 如果被设定为True, 被排序的序列里的元素会以降序输出, 这个参数默认为Flase(也就是升序排列)
key: 一个只有一个参数的函数,这个函数会被用在序列的每一个元素上,所产生的结果将是排序算法依赖的对比关键字。 key=str.lower来实现忽略大小写的排序,或者用key=len进行基于字符串长度的排序。
l = [28, 14, "28", 5, "9", "1", 0, 6] l.sort(key=int) print(l) >>> [0, '1', 5, 6, '9', 14, 28, '28'] l = [28, 14, "28", 5, "9", "1", 0, 6] l.sort(key=str) print(l) >>> [0, '1', 14, 28, '28', 5, 6, '9']
2.8 用bisect来管理已排序的序列
bisect模块包含两个主要函数: bisect(利用二分查找算法在有序列中查找元素), insort(利用二分查找算法在有序列中插入元素)
2.8.1 用bisect(haystack, needle)来查找元素的位置index
可以先用bisect(haystack, needle)查找位置index, 然后再用haystack.insert(index, needle)来插入新值。但是你也可以用insort来一步到位,并且后者的速度更快一些。
import bisect HAYSTACK = [1, 3, 4, 5, 6, 7, 8, 11, 22, 33, 44, 88] a = bisect.bisect(HAYSTACK, 9) print(a)
>>>
7
bisect函数其实是bisect_right函数的别名,后者还有一个姊妹函数叫 bisect_left.
示例: 根据一个分数,查找到他对应的成绩
import bisect def grade(score, breakpoints=[60, 70, 80, 90], grades="FDCBA"): i = bisect.bisect(breakpoints, score) return grades[i] scores = [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]] print(scores) >>> ['F', 'A', 'C', 'C', 'B', 'A', 'A']
2.8.2 用bisect.insort插入新元素
排序很耗时,因此在得到一个有序序列之后,我们最好能够保持它的有序。bisect.insort 就是为了这个为存在的。
import bisect HAYSTACK = [1, 3, 4, 5, 6, 7, 8, 11, 22, 33, 44, 88] bisect.insort(HAYSTACK, 9) print(HAYSTACK) >>> [1, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 88]
2.9 当列表不是首选时
如果我们需要一个只包含数字的列表, 那么array.array比list更高效。 数组支持所有跟可变序列有关的操作, 包括.pop、 .insert、.extend。
另外数组还提供从文件读取和存入文件的更快的方法 .frombytes .tofile
2.9.2 内存视图
memoryview是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切片。
memoryview + bytesarray 减少socket字符串拼接的消耗。
2.9.4 双向队列和其他形式的队列
利用.append和.pop方法我们可以把列表当作栈或者队列来用(.append, .pop(0)结合起来,模拟栈的“先进先出”),但是删除列表的一个元素之类的操作是很耗时的,因为这些操作会牵扯到移动列表里的所有元素。
collections.deque类(双向队列)是一个线程安全,可以快速从两端添加或者删除元素的数据类型。
import collections dq = collections.deque(range(10), maxlen=10) print(dq) >>> deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10) dq.rotate(3) print(dq) >>> deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10) dq.rotate(-4) print(dq) >>> deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10) dq.appendleft(-1) print(dq) >>> deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10) dq.extendleft([11,22,33]) print(dq) >>> deque([33, 22, 11, -1, 1, 2, 3, 4, 5, 6], maxlen=10)
append和popleft都是原子操作, 也就是说deque可以在多线程程序中安全地当作先进先出的栈使用,而使用者不用担心资源锁的问题。