Python 实用库介绍
bisect 管理有序列表
bisect 模块包含两个主要的函数,bisect 和 insort,两个函数都利用二分查找算法来在有序序列中查找和插入元素。
# BEGIN BISECT_DEMO
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) # <1>
offset = position * ' |' # <2>
print(ROW_FMT.format(needle, position, offset)) # <3>
if __name__ == '__main__':
if sys.argv[-1] == 'left': # <4>
bisect_fn = bisect.bisect_left
else:
bisect_fn = bisect.bisect
print('DEMO:', bisect_fn.__name__) # <5>
print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK))
demo(bisect_fn)
# END BISECT_DEMO
'''
DEMO: bisect
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
'''
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]
'''
其中 bisect 和 insort 函数(本质就是 bisect_left 和 insort_left)都有一个类似的 bisect_right 和 insort_right,他们的差别就是如果列表中存在计算结果相同的元素,那么新的元素将插入在这些元素的左边还是右边。
array 高效地数值存储结构
虽然列表既灵活又简单,但由于其是容器结构(其中存储的并不是具体数值,而是对应元素的指针,虽然这使得它应用范围更广,但所需要的访问和存储都要更大),因此如果存储的内容只是数值,则可以采用 array,其背后存的并不是 float 对象,而是数字的机器翻译。同时,array 支持几乎所有的 list 操作。
from array import array
from random import random
floats = array('d', (random() for i in range(10**7)))
print(floats[-1])
# 0.7030774022684948
fp = open("floats.bin", 'wb')
floats.tofile(fp)
fp.close()
floats = array('d')
fp = open("floats.bin", 'wb')
floats2.fromfile(fp, 10**7)
fp.close()
print(floats2[-1])
# 0.7030774022684948
print(floats == floats2)
# True
由于 array 只允许其中存储某类数值,因此在创建时需要指定存储的数据类型。具体的类型名称如下表所示。
memoryview 内存视图
memoryview 让用户在不复制内容的情况下操作同一个数组的不同切片。
import array
numbers = array.array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)
print(len(memv)) # 5
print(memv[0]) # -2
memv_oct = memv.cast('B')
print(memv_oct.tolist()) # [254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
memv_oct[5] = 4
print(numbers)
# array('h', [-2, -1, 1024, 1, 2])
其中创建了数据 numbers 的另一个别名 memv,在之后通过 memv.cast 实现了数据类型的转换。同时,如果修改 memv,那么原始数据也将被修改。
deque 双向队列
collections.deque 类是一个线程安全、可以快速从两端添加和删除元素的数据结构。而且如果想要有一种数据类型来存储“最近用到的几个元素”,deque 也是一个很好的选择。这是因为在新建一个双向队列的时候,你可以指定这个队列的大小,如果这个队列满员了,还可以从反向删除过期的元素,然后在尾端添加新的元素。
from collections import deque
dq = 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.extend([11, 22, 33])
print(dq) # deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
dq.extendleft([10, 20, 30, 40])
print(dq) # deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)
deque 支持几乎所有 list 操作,不过 deque 并不支持 pop(p) 操作。
queue 队列
其提供了同步(线程安全)类 Queue、LifoQueue 和 PriorityQueue,不同的线程可以利用这些数据类交换信息。这三个类的构造方法都有一个可选参数 maxsize,它接受正整数作为输入,用来限定队列的大小。但是在满员的时候,这些类不会扔掉旧的元素来腾出位置。相反,如果队列满了,它就会被锁住,知道另外的线程移除了某个元素而腾出了位置。这一特性让这些类很适合用来控制活跃线程的数量。
multiprocessing 这个包实现了自己的 Queue,它跟 queue.Queue 类似,是设计给进程间通信用的。同时还有一个专门的 multiprocessing.JoinableQueue 类型,可以让任务管理变得更方便。
asyncio 包有 Queue、LifoQueue、PriorityQueue 和 JoinableQueue,这些类专门为异步编程里的任务管理提供了专门的便利。