容器序列:list、tuple 和 collections.deque
可以存放不同类型的数据
扁平序列:str、bytes、array.array、bytearray
只能容纳一种类型
区别:容器序列中存放的是任意类型对象的引用,扁平序列里存放的是值而不是引用(就是连续的内存空间)。
可变序列:list、bytearray、array.array、deque 和 dict
不可变序列:tuple、str 和 bytes
列表推导和生成器表达式
列表推导:是构建列表的快捷方式,只能生成列表。
生成器表达式:用来创建其他任何类型的序列,生成列表以外的序列类型。
注意: 在列表推导式中有局部作用域,和函数类似,表达式内部的变量和赋值只会在局部起到作用。
列表推导式同filter和map的比较
>>> demo = 'abcde'
>>> result = [ord(i) for i in demo if ord(i) > 100]
>>> result
[101]
>>> result = list(filter(lambda i : i > 100, map(ord,demo)))
>>> result
[101]
生成器表达式
>>> demo = 'abcde'
>>> tuple(ord(i) for i in demo)
(97, 98, 99, 100, 101)
>>> import array
>>> array.array('I', (ord(i) for i in demo))
array('I', [97, 98, 99, 100, 101])
元组除了用作不可变列表,还可以用于没有字段名的记录。
namedtuple: collections.namedtuple是一个工厂函数,用来构建一个 带字段名的元组和一个有名字的类。
注意: namedtuple构建的类的实例消耗的内存跟元组一样,因为字段名都存在对应的类里。这个实例跟普通对象实例也要小,因为Python不会用__dict__
来存放这些实例的属性。
>>> from collections import namedtuple
>>> people = namedtuple('man', 'name sex age')
>>> demo = people('mac', 'joku', 21)
>>> demo
man(name='mac', sex='joku', age=21)
>>> demo.name
'mac'
>>> demo[2]
21
namedtuple:第一个参数是类名,第二个参数是类字段的名字,可以由字符串组成的可迭代对象或空格分割开的字符串。
>>> man_1 = ('max', 'laka', 23)
>>> demo1 = people(*man_1)
>>> demo1
man(name='max', sex='laka', age=23)
>>> demo1._fields
('name', 'sex', 'age')
_fields:属性是包含这个类所有字段名称的元组。
元组相对列表缺失的方法:append、clear、copy、extend、insert、reverse、sort、remove
一般对元素进行增、删、查、改在元组中都不存在。
支持切片:支持切片操作的序列类型有list、tuple、str
。
slice()函数实现切片对象,主要用在切片操作函数的参数传递。
class slice(stop)
class slice(start, stop[, step])
>>> myslice = slice(5)
>>> myslice
slice(None, 5, None)
>>> arr = range(10)
>>> arr[myslice]
range(0, 5)
>>> arr[0:5]
range(0, 5)
切片的赋值:如果把切片放在赋值语句左边,或把它作为del操作对象,就可以对序列进行嫁接、切除或修改。
>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20,30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11,22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100
Traceback (most recent call last):
File "" , line 1, in
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]
>>>
注意:赋值左边对象是切片,那么右侧就必须是可迭代对象。
这两种方法,在拼接过程中序列都不会被修改,都是新建一个同样类型的序列作为结果。
我们通常会用[[]] * 3来初始化一个由列表组成的列表,但是列表包含的三个元素是引用,这三个引用指向的是同一个列表。
>>> mylist = [['_'] * 3 for i in range(3)]
>>> mylist
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> youlist = [['_'] * 3] * 3
>>> youlist
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
mylist创建的二维列表在每次迭代中都新建一个列表。
youlist创建的二维列表实际指向同一个列表的引用。
增量赋值运算符*= 和 +=
取决于第一个操作对象。
+=:对应的特殊方法是__iadd__
,如果一个类没有实现改方法就会调用__add__
。
可变序列一般都实现了__iadd__
方法,不可变序列就不支持这个__iadd__
方法。
>>> l = [1, 2, 3]
>>> id(l)
4492677064
>>> l *= 2
>>> id(l)
4492677064
>>> t = (1, 2, 3)
>>> id(t)
4492681504
>>> t *= 2
>>> id(t)
4492551944
不可变序列进行重复操作,效率会很低,每次都有生成新对象,解释器需要将原来的对象中的元素复制到新对象中,再追加新元素。
>>> t = (1, 2, [3, 4])
>>> t[2] += [5,6]
Traceback (most recent call last):
File "" , line 1, in
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [3, 4, 5, 6])
上述代码对元组进行添加数据,虽然抛出了异常但是还是添加成功了,可以将+=替换为extend()。
list.sort:就地进行排序,不会复制原列表,返回值是None,未产生新的对象。
sorted:这是一个内置函数,可以接受任何形式的可迭代对象作为参数(可变序列或生成器),最终返回一个列表。
共同点:这两个参数都有两个可选关键字参数,reverse 和 key
,改排序算法是稳定的。
reverse:默认值为False升序排列。
key:一个只有一个参数的函数,改函数会作用在序列的每个元素上。
>>> demo = ['a', 'r', 'g', 'b', 'd']
>>> sorted(demo) # 进行升序排列
['a', 'b', 'd', 'g', 'r']
>>> demo
['a', 'r', 'g', 'b', 'd'] # 本身没有修改
>>> sorted(demo, key=len, reverse=True)
['a', 'r', 'g', 'b', 'd'] # 可见是稳定的
>>> sorted(demo, reverse=True)
['r', 'g', 'd', 'b', 'a'] # 降序排序
bisect模块包含函数bisect 和 insort
,两个函数都用二分查找算法在有序序列中查找或插入元素。
bisect(haystack, needle)在haystack里搜索needle的位置,使用二分搜索,必须保证haystack是有序序列。
>>> KEYS = [1,2,3,4,5,6,7,8,9,10]
>>> import bisect
>>> bisect.bisect(KEYS,10)
10
>>> bisect.bisect_right(KEYS,10)
10
>>> bisect.bisect_left(KEYS,10)
9
bisect:有两个可选参数lo和hi
,lo默认是0,hi默认是序列长度。
bisect是bisect_right函数的别名,返回查找元素的右边位置。
insort(seq, item)把变量item插入到序列seq中,并保持seq的升序顺序。
>>> KEYS = [1,2,3,4,5,6,7,8,9,10]
>>> import bisect
>>> bisect.insort(KEYS,9.1)
>>> KEYS
[1, 2, 3, 4, 5, 6, 7, 8, 9, 9.1, 10]
>>> bisect.insort(KEYS,11,lo=0,hi=5)
>>> KEYS
[1, 2, 3, 4, 5, 11, 6, 7, 8, 9, 9.1, 10]
>>>
insort:有两个可选参数lo和hi
,来控制查找范围。
insort是insort_right函数的别名,将元素插入在右边(比如在1==1.0值相同,但是类型不同的时候又用)。
上面的两种方法对所有序列类型都适用,如果处理数字列表数组是更好选择。
数组:当存放1000万个浮点数的话,数组(array)的效率要高,因为数组背后存的不是float对象,是数字的机器翻译,也就是字节表述。
双端队列:当需要频繁对序列做先进先出的操作,deque(双端队列)的速度会更快。
集合:将需要包含操作(检查元素是否存在集合中)频率高的时候,用set(集合)会更合适,set专为检查元素是否存在做过优化,但是它不是序列,set是无序。
需要只包含数字的列表,array.array
比list更高效。数组支持所有跟可变序列有关的操作,包括pop、insert、extend
,数组还提供从文件读取和存入文件的更快方法,如frombytes和tofile
。
创建数组需要一个类型码,表示在底层C语言应该存放什么样的数据类型。
>>> from array import array # 导入array类型
>>> from random import random
>>> floats = array('d', (random() for i in range(10**7))) # 生成双精度浮点数组(类型码'd'),用的可迭代对象是生成器表达式。
>>> floats[-1]
0.4019972618001888
>>> fp = open('/Users/hubo/floats.bin', 'wb')
>>> floats.tofile(fp) # 将数组存入二进制文件里
>>> fp.close()
>>> floats1 = array('d') # 新建一个双精度浮点数空数组
>>> fp = open('/Users/hubo/floats.bin', 'rb')
>>> floats1.fromfile(fp, 10**7) # 将存入的浮点数从二进制文件读取
>>> fp.close()
>>> floats1[-1]
0.4019972618001888
>>> floats == floats1 # 判断数组内容是否一样
True
array.fromfile:
从二进制文件里读取1000万个双精度浮点数只要0.1秒,比从文本里读取快60倍,因为后者使用内置的float方法把每行文字转换成浮点数。
array.tofile:
写入到二进制文件,比每行一个浮点数方式写入文本快7本。1000万个这样的数存在二进制文件只占用80 000 000
个字节,但是文本文件的话需要181 515 739
个字节。
快速序列化数字类型
使用pickle模块,pickle.dump处理浮点数组的速度几乎跟array.tofile一样快,但是使用pickle可以处理几乎所有内置数组类型,包含复数、嵌套集合、自定义的类。
数组的排序,从Python3.4后不支持list.sord()操作,需要使用sorted函数新建一个数组。
a = array.array(a.typecode, sorted(a))
如果经常和数组打交道,要学会使用memoryview(内置类),让用户在不复制内容的情况下操作同一个数组的不同切片。
>>> number = array('h', [-2, -1, 0, 1, 2]) # 生成短整形有符号的数组
>>> memv = memoryview(number) # 创建一个内存视图
>>> len(memv)
5
>>> memv[0]
-2
>>> memv_oct = memv.cast('B') # 将memv里的内容转换为'b'类型,无符号字符
>>> memv_oct.tolist() # 用列表的形式去查看
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
>>> memv_oct[5]=4 # 位置为5的字节赋值为4
>>> number # 占两个字节的整数的高位字节改为4,所以有符号的整数值变为1024
array('h', [-2, -1, 1024, 1, 2])
>>> memv_oct.tolist()
[254, 255, 255, 255, 0, 4, 1, 0, 2, 0]
利用append和pop方法,可以把列表当作栈或队列来用,但是删除列表的第一个元素(在第一个元素后添加元素)之类的操作都是很耗时的,因为会牵扯移动列表的所有元素。
colloections.deque
类双向队列是一个线程安全,可以快速从两端添加或删除元素的数据类型。
>>> from collections import deque
>>> dq = deque(range(10), maxlen=10) # maxlen是一个可选参数,代表队列容纳的元素数量,一旦设定不能修改。
>>> dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.rotate(3) # 队列旋转操作,当参数n>0,队列右边的n个元素移动到左边,反之。
>>> dq
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
>>> dq.rotate(-4)
>>> dq
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
>>> dq.appendleft(-1) # 当对已满(len(d)==d.maxlen)队列尾部添加操作,他的头元素会被删除掉。
>>> dq
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.extend([11,22,33]) # 在尾部添加三个元素
>>> dq
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
>>> dq.extendleft([44,55,66,77]) # 会把迭代器的元素添加到队列左边,元素逆序出现在队列里。
>>> dq
deque([77, 66, 55, 44, 3, 4, 5, 6, 7, 8], maxlen=10)
双向队列实现大部分列表的方法,也有自己的方法popleft和rotate
,为了实现这些方法,双向队列付出了一些代价,队列中间删除元素操作会慢,因为它只在头尾进行了优化。
append和popleft都是原子操作,deque可以在多线程中安全的当作栈使用,而不用担心资源锁的问题。
其他Python标准库中对队列的实现,queue、multiprocessing、asyncio、heapq
。