fluent python (二)python数据模型

fluent python (二)python数据结构

python的序列类型:

  1. 容器序列:

    list、tuple、collections.deque能存放不同类型的数据

  2. 扁平序列:

    str、bytes、bytearray、memoryview、array.array只能容纳一种类型

容器序列中存放的是他们所包含的任意类型的对象的引用,而扁平序列的存放的仅仅只是值。也就是说前者会对对象本身造成印象,而后者不会。扁平序列是一段连续的内存空间,它里面只能存放如字符、字节和数值这种基础类型。

其中tuple,str,bytes是不可变序列。

列表

列表推导:

1
2
3
word = 'huchi'
codes = [ord(x) for x in word]
print(codes)

这个就叫列表推导。

python2变量泄漏

1
2
3
x = 'huchi'
it = [x for x in 'huchi']
print(x)

在python2中会出现变量泄露。

列表推导同filter和map 的比较

1
2
3
4
5
6
word = 'huchi'
codes = [ord(x) for x in word if(ord(x) > 100)]
print(codes)

codes = list(filter(lambda c: c > 100, map(ord, word)))
print(codes)

谁快谁慢不一定

生成器表达式

1
2
3
4
word = 'huchi'
print(tuple(ord(x) for x in word))
import array
print(array.array(word[0], (ord(x) for x in word)))

如果生成器表达式是一个函数调用过程中唯一一个参数,那么不需要额外再用括号把他围起来,否则括号就是必须的。

元组

基本使用:

1
2
3
travel_id = [('huchi',17),('xiaoming',18),('abc',19)]
for student in sorted(travel_id):
print('%s %s' % student)

元组拆包

最好辨认的就是平行赋值,

1
2
huchi = ('huchi', 17)
name, age = huchi

我们可以使用*运算符把一个可迭代对象

1
2
3
print(divmod(9, 2))
t = (11, 3)
print(divmod(*t))

_也可以作为一个占位符,可以忽略我们不感兴趣的值

1
2
3
first, _, _, = (1, 2, 3)
print(first)
print(_)

用*来处理剩下的元素

1
2
a, b , *rest = range(5)
a, b , *rest = range(2)

具名元组

collections.namedtuple是一个工厂函数,可以用来创建一个带字段名的元组和一个有名字的类。所构建的类实例与元组所消耗的内存是一样的,比普通的对象小一点。

1
Card = collections.namedtuple('Card', ['rank', 'suit'])

创建一个具名元组需要两个参数,一个是类名,一个是类的各个字段的名字,可以是有数个字符串组成的可迭代对象,或者是由空格分开的字段名组成的字符串。

元组的构造函数只接受单一的可迭代对象。具名元组还有一些专门的属性。比如_fileds类属性,_make(iterable)类方法和实例方法_asdict()。

1
2
3
4
5
6
oneCard = Card('2', 'hearts')
print(oneCard._fields) # 类属性,返回包含这个类所有字段名称的元组
card_data = ('2', 'hearts')
newCard = Card._make(card_data) #也可以写成Card(*card_data)
print(newCard)
print(newCard._asdict()) # 把具名元组以collections.OrderedDict的形式而返回。

元组是一个强大的用来作为记录的数据类型,同时也是一个不可变的列表。

切片

像列表,元组,字符串这类序列类型都支持切片。

1
2
3
string = 'Hello huchi'
name = slice(6, None)
print(string[4::-1] , string[name])

简单的使用就是如此。

多维切片

外部库Numpy支持多维切片,如a[m:n, k:l]来得到二维切片。

给切片赋值

1
2
3
4
l = [i for i in range(10)]
l[2:5] = [20, 30]
del l[4:7:2]
l[3] = [100] # 不能写成l[3] = 100

对序列使用+和*

1
2
l = 5 * l
print(l)

+和*都不修改原有的操作对象,而是返回一个新的序列。

Tips: my_list = [[]]*3来初始化一个列表组成的列表的,得到的里面的3个元素其实是3个引用,且指向同一个列表。也就是修改期中一个,其他两个也都会改变

建立由列表组成的列表

正确操作:

1
2
3
4
5
6
7
8
9
10
11
12
#First
my_list = [['huchi'] * 3 for i in range(3)]
my_list[1][1] = 'ni'
print(my_list)

#Second
my_list = []
for i in range(3):
row = ['huchi'] * 3 # 每次生成的都是不同的对象
my_list.append(row)
my_list[1][1] = 'ni'
print(my_list)

错误演示

1
2
3
4
5
6
7
8
9
10
11
12
#First
wrong_list = [['huchi'] * 3] * 3
wrong_list[1][1] = 'ni'
print(wrong_list)

# Second
wrong_list = []
row = ['huchi'] * 3
for i in range(3):
wrong_list.append(row) #每次添加的都是同一个对象
wrong_list[1][1] = 'ni'
print(wrong_list)

序列的增量赋值

增量赋值运算符+=和*=的表现取决于他们的第一个操作对象。 += 的背后的特殊方法是__iadd__方法,如果没有就会退一步调用__add__方法。

对于可变序列来说,+=增量赋值运算符并没有改变他们的id,而对于不可变序列是会改变其id的,也就是是创建了新的对象了。

关于+= 的一个谜题

1
2
t = (1, 1, [1, 1])
t[2] += [2, 2]

他不仅报错了还改变了t.利用dis.dis()我们可以查看这句话的字节码,可以发现我们对列表更改完后,对元组赋值时出现了异常:t[2] = [1,1,2,2]这一步是出了错。

list.sort()排序与内置函数sorted排序

list.sort()会对原序列直接进行变更,而sorted则会新建一个列表作为返回值。而sorted()可以接受任何形式的可迭代对象作为参数,包括不可变序列或生成器。它最后返回的一定是一个列表。两种方式有相同的两个可选的参数:key,reverse.

可以使用key=len来按照字符串长度来排序,reverse就是是否翻转,也就是长度从小到大排序,但是长度相同保留相对位置。

1
2
3
4
list = ['huchi', 'chihu', 'ni', 'hao']
print(sorted(list, key=len))
print(list.sort(key=len, reverse=True))
print(list)

用bisect来管理已排序的序列

bisect模块主要包含两个函数:bisect和insort,两个函数都是利用二分来查找或插入元素。

bisect本身就是一个简单的二分搜索。这里展示一个demo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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(my_bisect):
for needle in reversed(NEEDLES):
position = my_bisect(HAYSTACK, needle)
offset = position * ' |'
print(ROW_FMT.format(needle, position, offset))


if __name__ == '__main__':

if sys.argv[-1] == 'left':
my_bisect = bisect.bisect_left
else :
my_bisect = bisect.bisect

print('DEMO:' + my_bisect.__name__)
print('haystack -> ', ' '.join('%2d' % n for n in HAYSTACK))
demo(my_bisect)
1
bisect = bisect_right   # backward compatibility

默认是查找的是第一个大于等于的也就是lower_bound(), bisect_left也就相当于upper_bound.

二分添加的话,也就是先找这个下标然后用insert()方法来添加进去。

不用列表

数组

1
2
3
4
5
6
7
8
9
10
from array import array
from random import random
floats = array('d', (random() for i in range(10**7))) # 类型码是'd',建立了一个双精度浮点空数组
print(floats[-1])
with open('floats.bin', 'wb') as fp:
floats.tofile(fp)
floats2 = array('d')
with open('floats.bin', 'rb') as fp:
floats2.fromfile(fp, 10**7)
print(floats2[-1])

读取二进制文件比文本文件快60倍,写入快7倍。

1
2
floats2 = array(floats2.typecode, sorted(floats2))
print(floats2[0], floats2[-1])

不能直接使用list.sort()这样直接排序,得像上面这样来排序。

内存视图

1
2
3
4
5
6
7
8
from array import array
numbers = array('h', [-2, -1, 0, 1, 2])
memv = memoryview(numbers)
print(len(memv))
memv_oct = memv.cast('B')
print(memv_oct.tolist())
memv_oct[1] = 3
print(numbers)

memoryview精确的修改了一个数组的某个字节。

优秀的NumPy 还有SciPy

1
2
3
4
5
6
7
8
import numpy
a = numpy.arange(12)
print(type(a), ':', a)
print('shape :', a.shape)
a.shape = (3, 4)
print(a)
print(a[:, 1]) # 多维切片
print(a.transpose()) # 行列交换
文章目录
  1. 1. fluent python (二)python数据结构
    1. 1.1. 列表
      1. 1.1.1. 列表推导:
      2. 1.1.2. python2变量泄漏
      3. 1.1.3. 列表推导同filter和map 的比较
      4. 1.1.4. 生成器表达式
    2. 1.2. 元组
      1. 1.2.1. 元组拆包
      2. 1.2.2. 具名元组
    3. 1.3. 切片
      1. 1.3.1. 多维切片
      2. 1.3.2. 给切片赋值
    4. 1.4. 对序列使用+和*
      1. 1.4.1. 建立由列表组成的列表
    5. 1.5. 序列的增量赋值
      1. 1.5.1. 关于+= 的一个谜题
    6. 1.6. list.sort()排序与内置函数sorted排序
    7. 1.7. 用bisect来管理已排序的序列
    8. 1.8. 不用列表
      1. 1.8.1. 数组
      2. 1.8.2. 内存视图
      3. 1.8.3. 优秀的NumPy 还有SciPy
|