接下来的第2,3,4章主要介绍了Python中最常用数据结构的Best Practice和进阶知识。
第2章介绍了Python中的标准库中的序列类型(sequence types)。sequence在Python中用的很多。这一章中主要是用法和技巧,很多涉及到的原理和特性都会在后面的章节补充讲解。
区分序列类型有主要根据两种分类方式:
- 按照所存储的对象分成两类:
- 可以放入不同类型变量的容器型序列(container sequences):
list
,tuple
和collections.deque
。 - 普通的序列(flat sequences):
str
,bytes
,bytearray
,memoryview
和array.array
等。
- 可以放入不同类型变量的容器型序列(container sequences):
- 根据是否可变
- 可变序列(mutable sequences) 有
list
,bytearray
,array.array
,collections.deque
和memeoryview
- 不可变序列(immutable sequences) 有
tuple
,str
和bytes
- 可变序列(mutable sequences) 有
下面主要是一些经典的或者稍微冷门进阶的笔记。
列表推导(list comprehensions)
最长用的序列类型应该就是列表(list)了。
在使用列表推导(list comprehensions) 最好用在生成一个新列表的时候:
1 | symbols = "[email protected]#$%" |
但在使用的时候最好自己判断,以保持简洁易读高效为准,不要为了列表推导就写好几层嵌套之类的。
Use your best judgment: for Python as for English, there are no hard and fast rules for clear writing”
Python2 and Python3
2 和 3 中有一个区别在于:
2中的变量会从作用域中泄漏:
1 | "test" a = |
3中修复了这个问题:
1 | "test" a = |
切片(slicing)
切片中有一个比较不常用的语法是三个冒号:
1 | "bicycle" s = |
对于seq[start: stop: step]
的内部调用是seq.__getitem__(slice(start, stop, step))
。 这里可以留意一下这个slice对象。书中给了一个用slice
对字符串处理的比较有意思的例子:
1 | """ data = |
使用*
和+
对序列进行修改
在使用+=
和 *=
的时候主要涉及到对可变对象和不可变对象不同的性能问题。 例如在对list调用 +=
的时候,Python首先会调用__iadd__
,如果实现了__iadd__
,那效果相当于extend
。 但如果没有实现__iadd__
这一特殊方法,就会退化到去调用__add__
, 这时候就变成了b = b + c
的情况,相当于先对b+c
求值,然后新生成了一个对象,速度比较慢。
书中最后给了一个很有意思的Puzzle:
判断下列输出结果:
1 | 1, 2, [30, 40]) t = ( |
那么结果t是什么呢?
- t 变成了(1,2, [30, 40, 50, 60])
- TypeError : ‘tuple’ object does not support item assignment
- Neither
- Both
结果竟然是(4), 在进行对t[2]操作的时候首先会报错,但是之后输出t查看又会是(1)的结果。书中通过查看生成的Bytecode给出了解答。
在这个超级冷门的问题上,作者给出了三点经验总结:
- 尽量不要在tuple中放入mutable items.
- 复合赋值(augmented assignment) 不是原子操作(atomic operation)
- 如果发生了奇怪的事情,可以去读Python bytescode。也很容易明白。
排序
在排序里主要是提到了标准库中的两种排序方式
list.sort
sorted
标准库函数
这里明确了一个Python API中的约定:如果函数或者方法对一个参数对象进行的原地修改(in-place) ,那返回值应该是一个None
,以此来告诉调用者这个对象已经被改变了,没有新对象被创建。这种约定也有一个小缺点,就是不能在进行流式接口编程(fluent interface)时候包含这种函数。
这里排序的第一种方法就是原地的,而sorted
函数则返回了一个新的对象。
使用bisect
bisetct中主要用了两个函数: 1. bisect
2. insort
。这里用了二分搜索算法在一个已排序的序列中更快的查找和插入。具体的操作可以看书上的例子。
List的替代品
list虽然好用但是在不一样的场景下会需要不一样的东西。例如1000万的浮点数,那用array会更好,因为array存储的是对象的bytes地址。有时候需要用FIFO之类队列,那么queue或者deque会更好。下面简单的列举一下,更详细的例子可以看书。
Arrays
如果全是数字的话,array会比list效率更好,并且在.frombytes
和.tofile
这些操作的时候更快。创建array需要提供一个类型字符,例如 ‘b’ 代表signed char
。
1 | from array import array |
memory views
内建的memorview
类是一个共享内存的序列类型(shared-memory sequence type)。这个和numpy中的数组一样,不需要bytes复制。
A memoryview is essentially a generalized Numpy array structure in Python self(without the math). It allows you to share memory between data-structures with out first copying. This is very important for large data sets.
NumPy 和 SciPy
NumPy 和 SciPy是众所周知的两个偏数学的库, 更加适合做数组和矩阵运算等等。这两个库也让Python在做科学计算领域十分出色。
Deques和其他队列
The class collections.deque is a thread-safe double-end queue designed for fast inserting and removing from both ends.
除了deque, 还有以下几种标准库中实现的队列:
- queue
- multiprocessing
- asyncio
- heaps