0%

Fluent Python Chapter2

接下来的第2,3,4章主要介绍了Python中最常用数据结构的Best Practice和进阶知识。

第2章介绍了Python中的标准库中的序列类型(sequence types)。sequence在Python中用的很多。这一章中主要是用法和技巧,很多涉及到的原理和特性都会在后面的章节补充讲解。

区分序列类型有主要根据两种分类方式:

  1. 按照所存储的对象分成两类:
    1. 可以放入不同类型变量的容器型序列(container sequences): list,tuplecollections.deque
    2. 普通的序列(flat sequences): str, bytes, bytearray,memoryviewarray.array等。
  2. 根据是否可变
    1. 可变序列(mutable sequences) 有 list,bytearray, array.array, collections.dequememeoryview
    2. 不可变序列(immutable sequences) 有 tuple, strbytes

下面主要是一些经典的或者稍微冷门进阶的笔记。

列表推导(list comprehensions)

最长用的序列类型应该就是列表(list)了。

在使用列表推导(list comprehensions) 最好用在生成一个新列表的时候:

1
2
symbols = "[email protected]#$%"
codes = [ord(symbol) for symbol in symbols]

但在使用的时候最好自己判断,以保持简洁易读高效为准,不要为了列表推导就写好几层嵌套之类的。

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
2
3
4
>>> a = "test"
>>> b = [a for a in "ABC"]
>>> a
'C'

3中修复了这个问题:

1
2
3
4
>>> a = "test"
>>> b = [a for a in "ABC"]
>>> a
"test"

切片(slicing)

切片中有一个比较不常用的语法是三个冒号:

1
2
3
>>> s = "bicycle"
>>> s[::3]
'bye'

对于seq[start: stop: step]的内部调用是seq.__getitem__(slice(start, stop, step))。 这里可以留意一下这个slice对象。书中给了一个用slice对字符串处理的比较有意思的例子:

1
2
3
4
5
6
7
8
9
10
11
12
>>> data = """
0.....6.....12
1994 bucketzxm
1995 bucketzxn
"""
>>> year = slice(0, 6)
>>> name = slice(6, 12)
>>> line_data = data.split("\n")[2:]
>>> for item in line_data:
... print(item[year], item[name])
1994 bucketzxm
1995 bucketzxn

使用*+ 对序列进行修改

在使用+=*=的时候主要涉及到对可变对象和不可变对象不同的性能问题。 例如在对list调用 +=的时候,Python首先会调用__iadd__,如果实现了__iadd__,那效果相当于extend。 但如果没有实现__iadd__这一特殊方法,就会退化到去调用__add__, 这时候就变成了b = b + c的情况,相当于先对b+c求值,然后新生成了一个对象,速度比较慢。

书中最后给了一个很有意思的Puzzle:

判断下列输出结果:

1
2
>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]

那么结果t是什么呢?

  1. t 变成了(1,2, [30, 40, 50, 60])
  2. TypeError : ‘tuple’ object does not support item assignment
  3. Neither
  4. Both

结果竟然是(4), 在进行对t[2]操作的时候首先会报错,但是之后输出t查看又会是(1)的结果。书中通过查看生成的Bytecode给出了解答。

在这个超级冷门的问题上,作者给出了三点经验总结:

  1. 尽量不要在tuple中放入mutable items.
  2. 复合赋值(augmented assignment) 不是原子操作(atomic operation)
  3. 如果发生了奇怪的事情,可以去读Python bytescode。也很容易明白。

排序

在排序里主要是提到了标准库中的两种排序方式

  1. list.sort
  2. 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
2
3
>>> from array import array
>>> from random import random
>>> floats = array('d', (random() for i in range(10**7)))

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, 还有以下几种标准库中实现的队列:

  1. queue
  2. multiprocessing
  3. asyncio
  4. heaps