Fluent Python 笔记(三):高效操作序列

544 阅读5分钟
原文链接: zhuanlan.zhihu.com

目录:

Fluent Python 笔记(一):数据模型

Fluent Python 笔记(二):序列基础

Fluent Python 笔记(三):高效操作序列

本篇主要讲序列的通用操作,如切片,运算符。以及序列的排序,搜索。还有一些遇到性能瓶颈时,用于取代 list 的数据结构。

切片(Slice)

切片是序列中非常常用的操作,基本格式是 seq[start:stop:step],其中step及之前的冒号均可以省略,表示 step 为1。start和stop也都可以省略,但是第一个冒号不能省略。start省略表示从头开始,stop省略表示切到尾部。

切片操作适用于几乎所有序列类型,如 list, tuple, str 等等。

示例:

>>> l = [10, 20, 30, 40, 50, 60]
>>> l[:2] # split at 2
[10, 20]
>>> l[2:]
[30, 40, 50, 60]
>>> l[2:4]
[30, 40]

Python 中表示范围一般使用前闭后开 [start, stop),根据这两个单词也比较容易记忆。

切片的长度,自然也就是 stop - start。

切片对象:

符号 a:b:c 只有在 [] 中作为索引使用时才有意义,产生了一个切片对象:slice(a, b, c)。

在计算 seq[start:stop:step]时,Python 调用 seq.__getitem__(slice(start, stop, step))。

切片对象是可以命名的(实际上 Python 中的赋值都是把名称绑定到对象上):

>>> MIDDLE = slice(2:4)
>>> l[MIDDLE]
[30, 40]

多维切片:

很少会用到,但需要记住的是:

计算 a[i, j] 时,Python 调用 a.__getitem__((i, j))。

切片赋值:

我们可以把可变序列中的某个条目替换为其他值,当然也可以把其中的一个切片对应的部分替换为另一个序列,长度可以不同。也可以把这部分删除。

示例:

>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]


需要注意:

  • 不可变序列不能这样赋值。(这是很显然的)
  • 给切片赋值的时候,只能用序列。(不能用单个条目)

序列的运算符操作

加法示例:

>>> [1, 2] + [3, 4, 5]
[1, 2, 3, 4, 5]

加号两边的序列类型必须相同。

乘法示例:

>>> l = [1, 2, 3]
>>> l * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 5 * 'abcd'
'abcdabcdabcdabcdabcd'

加法和乘法都是创建一个新的序列,之前的序列都不会修改。

复合赋值:

在执行 += 的时候, Python 会首先检查 __iadd__ 函数有没有实现,如果没有,则 fall back 到 __add__ 。

执行 a += b 后,对于可变序列,a 的地址不会改变,对于不可变序列,a 的地址会改变。

排序

对于 list 来说,有两种排序方式:

  • list.sort

对一个 list 就地排序,并返回 None 来提醒我们没有创建新的 list

(这是一个重要的 API 惯例:就地改变对象的函数和方法应该返回 None,从而明确地告诉调用者对象本身改变了,而不是创建了新的对象)

示例:

>>> fruits = ['grape', 'raspberry', 'apple', 'banana']
>>> fruits.sort()
>>> fruits
['apple', 'banana', 'grape', 'raspberry']
  • sorted 函数

应用于一个列表上,并产生一个新的,已排序的列表,不改变之前的列表。

管理有序序列

在经过上面的方式排序以后,常用的操作有查找和插入。

这两个操作都使用 bisect 模块。

  • 使用 bisect.bisect_left 和 bisect.bisect_right 进行搜索

这两个函数都可以在一个有序序列中搜索 target。

  • 使用 bisect.insort 进行插入操作

排序是比较昂贵的操作,如果已经有了排好序的序列,可以用这个函数保持有序。

(这两个函数具体使用的时候查阅文档,常用的话就记住,不常用就了解下好了)

什么时候不该使用 list ?

由于 list 是一个非常方便的结构,写程序时不由自主就会首先考虑它,大部分情况下是没什么问题的,然而如果遇到了性能瓶颈,就需要考虑使用其他数据结构来替代 list。
  • 如果存的全是数字,那么使用 array.array

如果需要大量操作数字,请使用 memoryview

如果还不够,请使用科学计算库 NumPySciPy

  • 如果经常从两端添加删除元素,请使用 collections.deque

deque 是一个线程安全的双端队列,设计用来快速从两端插入删除。

可以设定最大长度,插入的时候如果超出了最大长度,就从对面忽略元素。

deque 实现了大部分 list 的方法,并添加了一些如 popleftrotate

append 和 popleft 都是原子操作,所以 deque 作为 LIFO 队列在多线程应用中是安全的,不需要加锁。

  • 如果有大量的包含检测(if item in my_collection),使用集合更好,集合为包含检测做了优化,保证极高的速度进行成员检测,当然,需要注意的是集合是无序的。