数据结构
序列数组
内置类型
-
容器序列: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直接排序,返回Nonesorted(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 |
asyncio | python3.4新包,有Queue、LifoQueue、PriorityQueue、JoinableQueue,为异步编程提供便利。 |
heapq | 没有队列类,提供了heappush和heappop方法,可以把可变序列当作堆队列或者优先队列来使用。 |