【读书笔记】 - 《流畅的python》02-数据结构(列表、数组、元组、切片、队列)

数据结构

序列数组

内置类型

  • 容器序列:list、tuple、collections.deque这些序列可以存放不同类型的数据。

  • 扁平序列:str、bytes、bytearray、memoryview和array.array只能容纳一种类型。里面是一段连续的内存空间。

  • 可变序列(MutableSequence):list、bytearray、array.array、collections.deque、memoryview

  • 不可变序列(Sequence):tuple、str、bytes

不可变序列有__getitem__ __contains__ __iter__ __reversed__ index count 等方法

可变序列有__setitem__ delitem__ insert append reverse extend pop remove __iadd__等方法

列表推导

  • python会忽略括号内换行([]、{}、()),因此列表推导的时候可以省略续行符\
  • 列表推导可以代替map/filter的组合。但尽量在合适且易读的情况下使用。

举例:

x = [1,-2,3,-9,0,1]
positive = [ abs(i) for i in x]

positive = [1,2,3,9,0,1]

笛卡尔积:
计算颜色与尺寸的组合,也就是两个列表相乘构成元组列表。

colors = ["black","white"]
sizes = ['S','M','L']
results = [ (color, size) for color in colors for size in size  ]

最后results中的排列顺序则先排颜色(先是black对应三个尺寸,在是white对应三个尺寸),如果用for循环实现,则colors是外层的循环,size是内层的。

生成器表达式
与列表推导类似,但是是圆括号构成,遵守迭代器协议,可以逐个地产生元素。
举例:

# 元组tuple
test="exαmp1e"
print(tuple( ord(x) for x in test))
# 数组array
import array
print(array.array('I',  (ord(x) for x in test)))

输出:

(101, 120, 945, 109, 112, 49, 101)
array('I', [101, 120, 945, 109, 112, 49, 101])

元组和记录

元组是对数据的记录,每个元素都存放了记录中一个字段的数据和这个字段的位置。

拆包(unpacking)
对元组进行拆包(unpacking):

# 赋值
position = (0.2, 0.9, 1,1)
x,y,z = position

# 交换
x, z = z, y

# 把可迭代对象拆开作为函数的参数
test = (x*20, y*100)
newX, newY = divmod(*test)
print(newX, newY)

# 为了保持参数数目一致,用下划代替
x, y, _ = position

# 用*处理多余的参数
x, *pos = position
print(x, pos)
*pos,z = position
print(pos, z)

元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是被迭代时,元素数量要保持一致,除非用*来处理多余的元素。

嵌套元组拆包

test = [ (‘beijing’, (1,2))
		 (‘chengdu’, (3,4))]
for city, (x,y) in test:
	print(city)
	print(x/y)

具名元组
利用工厂函数 collections.namedtuple构建带字段名的元组和一个有名字的类(便于调试)。
具名元组可以快速构造类,但和字典不同(字典可以修改,具名元组不可以)

from collections import namedtuple
Student = namedtuple('Student', 'name age gender')
xiaoming = Student('xiaoming', 16, 'boy')
print(xiaoming)
 Student(name='xiaoming', age=16, gender='boy')
  • 通过具名元组构造的类的对象,消耗的内存比一般的对象实例更小,python解释器不会用__dict__来存储实例的属性。
  • 如上例所示,namedtuple传入的两个参数分别是类名,类的各个属性名(用空格键隔开),两个参数都是字符串。namedtuple(className,properties)
  • 属性(字段)的赋值,需要在调用构造函数时传参。并且可以通过属性名(字段名)和位置来获取信息:例如:print(xiaoming[1], xiaoming.age) 这两个是等价的。

具名元组的属性和方法:

  • _fields:属性,包括这个类的所有字段名称的元组,xiaoming._fields输出('name', 'age', 'gender')
  • _make(*item):通过接受一个可迭代对象(例如一个元组)来生成这个类的一个实例
  • _asdict():把具名元组以collections.OrderedDict的形式返回,我们可以利用它来把元组里面的信息友好地呈现出来。xiaoming._asdict()输出{'name': 'xiaoming', 'age': 16, 'gender': 'boy'}

切片

列表、元组、字符串都支持切片s[start: end: interval]
start是起始,end是结束的索引(但不会被包含在内),interval指的是对s在start和end之间以interval为间隔取值。start和end可以不写。
interval可以取负值,当interval为负值时,可以理解为还是从start到end以|interval|为间隔取值,但结果经过了一次逆序。
切片可以赋值,但赋值语句右边必须也为可迭代对象。

# 取切片
s='bicycle'
# 输出结果
s[::3] = 'bye'
s[::-1] = 'elcycib'

+与*

这一节平时用的比较多就不赘述。
但关于+=的谜题很有意思:
即:


>>> t = (1,2,[30,40])
>>> t[2]+= [50,60]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
>>> t = (1,2,[30,40])
>>> t[2].extend([50,60])
>>> t
(1, 2, [30, 40, 50, 60])

t这个元组中,t[2]是列表,是一个可变对象,而s是不可变的元组,因此赋值过程中会报错。

  • 尽量不要把可变对象放到元组内。
  • 增量赋值+= 不是原子操作
  • 可以用dis.dis('s[a]+=b')查看这一操作的字节码

排序

  • list.sort(): 对list直接排序,返回None
  • sorted(list): 返回排序好的新list,而不改变原list
  • 第一个参数是需要排序的list,第二个参数是reverse(默认为False),第三个参数是key
    min()max()也有key这个参数,用来定义排序规则的函数关键字。key的默认值为恒等函数(identity function),也就是元素的值。若list中每个值是字符串,key=str.low意思是忽略大小写,key=len的意思是按照字符串的长度来排,推测sort函数在对比时会调用key这个函数。

这两个函数背后的排序算法为Timsort,会根据原始数据特点交替使用插入排序和归并排序

bisect
利用二分查找算法来在有序的序列中查找或者插入新的元素。

  • bisect.bisect:搜索,bisect.bisect(list, search_list), list是原序列,search_list是要搜索(若不存在则插入)的数据,返回搜索到的数据的位置,如果这个位置有和搜索目标相等的数据,则返回后一个位置(插在后面);bisect.bisect_left(list, insert_list) 返回的位置则相反,如果有相等数据,就返回该位置,插在这个数据前面。

反过来也可以利用这个搜索功能建立索引,实现一一对应的功能:

def grade(score, breakpoints=[60,70,80,90],grades="FDCBA"):
	i = bisect.bisect(breakpoints, score)
	return grades[i]

print( grade(score) for score in [33, 99, 77, 70, 89])
# 输出
['F', 'A', 'C', 'C', 'B']
  • bisect.insort: 插入新元素且保持序列升序,和bisect一样有可选参数来控制查找的范围:lo,默认为0,hi默认为序列的长度len。

列表list有时候不是首选,存放大量浮点数时数组更高效,需要频繁先进先出的话双端队列deque更高效。

数组

只包含数字时,array.array比list更高效,因为里面存储的是数字的机器翻译(字节表达)。
数组支持.pop .insert .extend等方法,以及从文件读写的.frombytes .tofile方法。
举例:

from array import array
floats = array('d', (0 fro i in range(20)))

pickle.dump和 array.tofile几乎一样快,而且pickle可以处理所有内置数字类型(复数、嵌套集合、简单自定义类等)

array与list对比:

array有但list没有的属性和方法:s.byteswap s.frombytes(b) s.fromfile(f,n) s.fromlist(l) s.tobytes() s.tofile() s.tolist() s.typecode s.__deepcopy__() s.__copy__()
s.byteswap是翻转数组内每个元素的字节序列,转换字节序。
s.typecode是返回只有一个字符的字符串,代表数组元素在C语言中的类型。

反之只有list拥有的属性和方法:s.clear() s.copy() s.sort()
其他方法例如append、__contains__、extend、remove、 pop 、reverse都是通用的了。

内存视图memoryview

能让用户在不复制内容的情况下操作同一个数组的不同切片

内存视图其实是泛化和去数学化的NumPy数组,任意数据结构都可以共享内存

我理解的是操作内存视图的时候,操作的是该地址空间上的数据。因为memoryview是一个连续的地址空间,所以切片操作是完全可行的。就像指针一样,避免了反复的数据复制。

后文中numpy和scipy因为用的比较多,也有别的文章和文档,就不再记录。

队列

双向队列collections.deque

线程安全,快速从两端添加和删除元素的数据类型。
一般的列表拥有append、pop方法可以模拟栈的操作,双向队列多出了append_left、pop_left方法

list.pop(p) 默认p为最后一个数的目录,p可以传参,而deque.pop()不传参,只能弹出最后一个数。

其他队列

名称描述
queue提供了同步(线程安全)类Queue、LifoQueue和PriorityQueue,不同线程可以利用这些数据类型交换信息。可选参数maxsize会限定队列的大小,但满员时不会丢掉旧的数据,而是锁住,直到某个线程为它腾出位置。
multiprocessing这个包实现了自己的Queue,供进程间通信使用。还有multiprocessing.JoinableQueue
asynciopython3.4新包,有Queue、LifoQueue、PriorityQueue、JoinableQueue,为异步编程提供便利。
heapq没有队列类,提供了heappush和heappop方法,可以把可变序列当作堆队列或者优先队列来使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canmoumou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值