内存视图1
memoryview
是一个内置类,它能让用户在不复制内容的情况下操作同一个数组的不同切片。memoryview
的概念受到了 NumPy 的启发(参见2.9.3 节)。Travis Oliphant 是 NumPy 的主要作者,他在stackoverflow回答“ Whenshould a memoryview beused?”中对memoryview
的功能做了说明2。
memoryview.cast
的概念跟数组模块类似,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。这听上去又跟 C 语言中类型转换的概念差不多。memoryview.cast
会把同一块内存里的内容打包成一个全新的 memoryview
对象给你。
通过改变数组中的一个字节来更新数组里某个元素的值:
numbers = array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers) # 利用含有5个短整型有符号整数的数组创建一个memoryview。
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])
NumPy3和SciPy
凭借着 NumPy 和 SciPy4 提供的高阶数组和矩阵操作,Python 成为科学计算应用的主流语言。
对 numpy.ndarray
的行和列进行基本操作:
import numpy
a = numpy.arange(12)
print(a) # array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
print(type(a)) # <class 'numpy.ndarray'>
print(a.shape) # (12,)
a.shape = 3, 4
print(a)
# array([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]])
print(a[2]) # array([ 8, 9, 10, 11])
print(a[2, 1]) # 9
print(a[:, 1]) # array([1, 5, 9])
print(a.transpose())
# array([[ 0, 4, 8],
# [ 1, 5, 9],
# [ 2, 6, 10],
# [ 3, 7, 11]])
NumPy 也可以对numpy.ndarray
中的元素进行抽象的读取、保存和其他操作:
import numpy
floats = numpy.loadtxt('floats-10M-lines.txt')
print(floats[-3:])
array([3016362.69195522, 535281.10514262, 4566560.44373946])
floats *= .5
print(floats[-3:])
array([1508181.34597761, 267640.55257131, 2283280.22186973])
from time import perf_counter as pc
t0 = pc()
floats /= 3
print(pc() - t0) # 0.03690556302899495
numpy.save('floats-10M', floats)
floats2 = numpy.load('floats-10M.npy', 'r+')
floats2 *= 6
print(floats2[-3:]) # memmap([3016362.69195522, 535281.10514262, 4566560.44373946])
双向队列5和其他形式的队列
利用 .append
和 .pop
方法,我们可以把列表当作栈或者队列来用(比如,把 .append
和 .pop(0)
合起来用,就能模拟栈的“先进先出”的特点)。但是删除列表的第一个元素(抑或是在第一个元素之前添加一个元素)之类的操作是很耗时的,因为这些操作会牵扯到移动列表里的所有元素。
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)
列表和双向队列的方法(不包括由对象实现的方法):
方法或属性 | 列表 | 双向队列 | 详解 |
---|---|---|---|
s.__add__(s2) | ● | s + s2,拼接 | |
s.__iadd__(s2) | ● | ● | s += s2,就地拼接 |
s.append(e) | ● | ● | 在尾部添加一个新元素 |
s.appendleft(e) | ● | 在尾部添加一个新元素 | |
s.clear() | ● | ● | 删除所有元素 |
s.__contains__(e) | ● | s 是否包含 e | |
s.copy() | ● | 列表的浅复制 | |
s.__copy__() | ● | 对 copy.copy(浅复制)的支持 | |
s.count(e) | ● | ● | e 在 s 中出现的次数 |
s.__delitem__(p) | ● | ● | 把位于 p 的元素删除 |
s.extend(i) | ● | ● | 将可迭代对象 i 中的元素添加到尾部 |
s.extendleft(i) | ● | 将可迭代对象 i 中的元素添加到头部 | |
s.__getitem__(p) | ● | ● | s[p],获取位置 p 的元素 |
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() 6 | ● | ● | 删除最后或者是(可选的)位于 p 的元素,并返回它的值 |
s.popleft() | ● | 移除第一个元素并返回它的值 | |
s.remove(e) | ● | ● | 删除 s 中的第一次出现的 e |
s.reverse() | ● | ● | 就地把 s 的元素倒序排列 |
s.__reversed__() | ● | ● | 返回 s 的倒序迭代器 |
s.rotate(n) | ● | 把 n 个元素从队列的一端移到另一端 | |
s.__setitem__(p,e) | ● | s[p] = e,把元素 e 放在位置p,替代已经在那个位置的元素 | |
s.sort([key],[reverse]) | ● | 就地对 s 中的元素进行排序,可选的参数有键(key)和是否倒序(reverse) |
除了 deque 之外,还有些其他的 Python 标准库也有对队列的实现。
queue7:
提供了同步(线程安全)类 Queue
、LifoQueue
和PriorityQueue
,不同的线程可以利用这些数据类型来交换信息。这三个类的构造方法都有一个可选参数 maxsize
,它接收正整数作为输入值,用来限定队列的大小。但是在满员的时候,这些类不会扔掉旧的元素来腾出位置。相反,如果队列满了,它就会被锁住,直到另外的线程移除了某个元素而腾出了位置。这一特性让这些类很适合用来控制活跃线程的数量。
multiprocessing8:
这个包实现了自己的 Queue,它跟 queue.Queue
类似,是设计给进程间通信用的。同时还有一个专门的multiprocessing.JoinableQueue
类型,可以让任务管理变得更方便。
asyncio:
Python 3.4 新提供的包,里面有Queue
、LifoQueue
、PriorityQueue
和 JoinableQueue
,这些类受到 queue
和 multiprocessing
模块的影响,但是为异步编程里的任务管理提供了专门的便利。
heapq9:
跟上面三个模块不同的是,heapq
没有队列类,而是提供了heappush
和 heappop
方法,让用户可以把可变序列当作堆队列或者优先队列来使用。
杂谈
Python 入门教材往往会强调列表是可以同时容纳不同类型的元素的,但是实际上这样做并没有什么特别的好处。元组则恰恰相反,它经常用来存放不同类型的的元素。这也符合它的本质,元组就是用作存放彼此之间没有关系的数据的记录。
python进阶书目串烧(一)—— 特殊方法、序列数组、列表推导、生成器表达
python进阶书目串烧(二)—— 元组拆包、具名元组、元组对比列表、切片
python进阶书目串烧(三)—— 序列、排序、列表对比数组
python进阶书目串烧(四)—— 内存视图、NumPy、列表对比双向队列
python进阶书目串烧(五)—— 泛映射类型、字典推导、映射的弹性键查询
python进阶书目串烧(六)—— 字典变种、不可变映射类型、集合推导
python进阶书目串烧(七)—— 字典原理、字典与集合特征对比
B2中涉及到
memoryview
使用的位置:5.9 读取二进制数据到可变缓冲区中、5.10 内存映射的二进制文件、6.12 读取嵌套和可变长二进制数据、11.13 发送与接收大型数组 ↩︎内存视图其实是泛化和去数学化的
NumPy
数组。它让你在不需要复制内容的前提下,在数据结构之间共享内存。其中数据结构可以是任何形式,比如 PIL 图片、SQLite 数据库和NumPy
的数组,等等。这个功能在处理大型数据集合的时候非常重要。 ↩︎B2中涉及到
numpy
使用的位置:3.6 复数的数学运算、3.9 大型数组运算、3.10 矩阵与线性代数运算、6.11 读写二进制数组数据、11.13 发送与接收大型数组、15.3 编写扩展函数操作数组、15.11 用 Cython 写高性能的数组操作 ↩︎SciPy 是基于
NumPy
的另一个库,它提供了很多跟科学计算有关的算法,专为线性代数、数值积分和统计学而设计。SciPy 的高效和可靠性归功于其背后的 C 和 Fortran 代码,而这些跟计算有关的部分都源自于Netlib 库。换句话说,SciPy 把基于 C 和 Fortran的工业级数学计算功能用交互式且高度抽象的 Python 包装起来,让科学家如鱼得水。 ↩︎B2中涉及到
collections.deque
使用的位置:1.3 保留最后 N 个元素、4.6 带有外部状态的生成器函数、12.12 使用生成器代替线程 ↩︎a_list.pop(p)
这个操作只能用于列表,双向队列的这个方法不接收参数。 ↩︎B2中涉及到
queue
使用的位置:7.11 内联回调函数、12.3 线程间通信、12.7 创建一个线程池、12.10 定义一个 Actor 任务、12.13 多个线程队列轮询;B3中涉及到queue
使用的位置:第39条:用Queue来协调各线程之间的工作 ↩︎B2中涉及到
multiprocessing
使用的位置:7.8 减少可调用对象的参数个数、7.11 内联回调函数、11.7 在不同的 Python 解释器之间交互、11.8 实现远程方法调用、11.11 进程间传递 Socket 文件描述符、12.1 启动与停止线程、12.9 Python 的全局锁问题 ↩︎B2中涉及到
heapq
使用的位置:1.4 查找最大或最小的 N 个元素、1.5 实现一个优先级队列、4.15 顺序迭代合并后的排序迭代对象、12.2 判断线程是否已经启动 ↩︎