流畅的python python 序列
内置序列
- 容器类型
- list 、tuple和collections.deque这些序列能放入不同的类型的数据
- 扁平序列
- str、byets、bytearray、memoryview(内存视图)和array.array(数组)
- 可变序列
- list、bytearray、array.array、collections.deque和memoryview
- 不可变序列
- tuple、str和bytes
可变序列所拥有的方法是在不可变序列的基础上增加的.
列表推导式
简单使用
列表是我们常见的可变序列,他是容器类型的这里主要介绍列表推导式
列表推导式是构建列表的快捷方式
例子:
将一个字符串转换成Unicode码位的列表
symbols='abcde' code=[] for item in symbols: code.append(ord(item)) print(code) #[97, 98, 99, 100, 101]
再来看列表推导式:
symbols='abcde' code=[ord(item) for item in symbols] print(code)
- 列表推导式能让你的程序变的更加简洁
- 列表推导式只用来创建新的列表,并且尽量保持简短
- 列表推导式可以帮助我们把一个序列或者其他可迭代的对象中的元素过滤或者是加工,然后再创建一个新的列表
列表推导式的过滤
借助刚刚的例子,我想得到一个Unicode码位大于99的,那么我们用内置函数map和filter来构造看看:
symbols='abcde' filter_symbols=list(filter(lambda x:x>99,map(ord,symbols))) print(filter_symbols) #[100, 101]
那么列表推导式呢?
symbols='abcde' filter_symbols=[ord(item) for item in symbols if ord(item)>99] print(filter_symbols) #[100, 101]
从上面的例子可以看出来,列表推导式更加具有可读性
注意列表推导式变量泄露问题
在python2.x中for的关键字可能会影响到其他同名的变量
例子:
x='变量泄露' result=[x for x in 'ABC'] print(x) #'C'
x原本的值被取代了,这不是我们想要看到的,那么这就叫变量泄露
在python3中是不会出现的.
x='变量泄露' result=[x for x in 'ABC'] print(x)#变量泄露
列表推导式/生成器表达式/以及集合和字典的推导在python3中都有了自己的局部作用域.所以表达式的上下文中的变量还可以变正常的使用
元组
元组和记录
元组其实是对数据的记录,如果紧紧理解元组是不可变的列表,那么我们可能忽略了他的位置信息的重要性,
如果把元组当做一些字段的集合,那么数量和位置就显的异常重要
name,age=('ming',12) year,month,day = (2018,8,16) tup=('ming','大帅比') print('%s:%s'%tup)
那么上面的例子就可以看出位置信息的重要性,那么列表也可以实现上面的拆包机制,但是为什么不用列表?
因为元组的是不可变序列的,如果你用列表来进行位置的拆包,如果你的列表insert了一个值,那么你的信息将会全部乱套.
元组的拆包
在上面将元组的元素分别赋值给变量,name,age同样用一个%运算符就把tup的元素对应到了格式字符串的空档中,这些都是对元组拆包的应用
元组的拆包可以应用到任何可迭代的对象上,但是被可迭代对象的元素数量必须跟接收这些元素的空档数一致,除非我们用*
不用中间变量交换两个变量的值
a,b=b,a
用*运算符把一个可迭代对象拆开作为函数的参数:
t=(20,8) q,r=divmod(*t) print(q,r) #2,4
用*在处理剩下的元素
a,b,*rest=range(5) print(a,b,rest)#0 1 [2, 3, 4] a,b,*rest=range(3) print(a,b,rest)#0 1 [2] a,b,*rest=range(2) print(a,b,rest)# 0 1 []
*可以出现在赋值表达式的任意位置
a,*rest,b=range(5) print(a,rest,b)#0 [1, 2, 3] 4 *rest,a,b=range(5) print(rest,a,b)#[0, 1, 2] 3 4
切片
在python中,像列表/元组/字符串这类数列支持切片操作
为什么切片和区间会忽略最后一个元素
- 当只有最后一个位置信息时,我们可以快速看出切片和区间的元素
- range(3), my_list=[:3]
- 当起始和终止位置可见时,可以快速计算出切片和区间的长度,用最后一个下标减去第一个下标
- 分割成不重复的两部分
- my_list[:x] my_list[x:] 那么这两个切片是不存在重复的元素
切片的赋值:
l=list(range(10)) l[2:5]=[20,30] print(l)#[0, 1, 20, 30, 5, 6, 7, 8, 9] del l[5:7] print(l)#[0, 1, 20, 30, 5, 8, 9] l[3::2]=[11,22] print(l)#[0, 1, 20, 11, 5, 22, 9] # l[2:4]=100'#报错 切片的赋值必须是可迭代的对象 # print(l) l[2:4] ='abcde' #赋值范围超过切片范围,同样的会把赋值的内容全部放入列表中 #[0, 1, 'a', 'b', 'c', 'd', 'e', 5, 22, 9] 且不会挤出列表原有的元素
对序列使用+和*/增量赋值
对序列使用+和*
+和*都遵循不修改原有的操作对象,而是构建一个新的序列
board=[['_']*3 for i in range(3)] board[1][2]='x' print(board)#[['_', '_', '_'], ['_', '_', 'x'], ['_', '_', '_']] #列表推导式,每次执行的时候都会新创建一个['_'] weird_board=[['_']*3]*3 weird_board[1][2]='x' print(weird_board)#[['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']] #如果直接在本身列表里*嵌套的列表,那么里面嵌套的列表都是指同一个对象 #那么可能结果不是你想要的
序列的增量赋值
对于可变的序列来说,+=,*=内部调用了可变序列对象的__iadd__方法或者__imul__方法
那么该可变序列就会就地改动,并不会指向新的对象,就是说他的id是不变的
l=[1,3,4] print(id(l)) l*=4 print(id(l)) #60606088 #60606088
对于不可变序列来说+=和*=会退一步的调用__add__,和__mul__方法
那么该方法会进行重新的赋值拼接操作,然后追加到新的元素中
l=(1,3,4) print(id(l)) l*=4 print(id(l)) #43408192 #43626216
- 对不可变序列进行重复拼接操作的话,效率会很低,因为每次都有一个新对象,而且解释器需要把原来的对象中的元素先赋值到新的对象里,然后再追加新的元素
+=的一个有趣例子:
t=(1,2,[30,40]) t[2]+=[50,60] #运行后,代码报错 #但是再打印t发现元素已经修改了 #我们可以用捕捉异常来看 try: t=(1,2,[30,40]) t[2]+=[50,60] except Exception as e: print(t)#(1, 2, [30, 40, 50, 60])
我们可以看到t[2]=t[2]+[50,60],先进行列表的相加,我们知道这一步是可以实现的,但是当我们赋值到t[2]的时候,因为t是一个元组是不可以修改的序列当然就报错了
但是我们的t[2]+[50,60]这一部已经执行了,就是说t[2]列表对象的值已经被修改了
所以在报错的同时元组也被修改了
bisect模块
bisect模块包含两个主要函数,bisect和insort,两个函数都是利用二分查找算法来在有序的序列中查找或者插入元素
用bisect来搜索
bisect(haystack,needle)在haystack(干草垛)里搜索needle(针)的位置,haystack必须是一个有序的序列.
例子:
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(bisec_fn): for needle in reversed(NEEDLES): position = bisec_fn(HAYSTACK,needle) offset = position* ' |' print(ROW_FMT.format(needle,position,offset)) if __name__ == '__main__': if sys.argv[-1] == 'left': bisect_fn = bisect.bisect_left else: bisect_fn = bisect.bisect print('HAYSTACK->',' '.join('%2d'%n for n in HAYSTACK)) demo(bisect_fn) HAYSTACK-> 1 4 5 6 8 12 15 20 21 23 23 26 29 30 31@14 | | | | | | | | | | | | | |31 30@14 | | | | | | | | | | | | | |30 29@13 | | | | | | | | | | | | |29 23@11 | | | | | | | | | | |23 22@ 9 | | | | | | | | |22 10@ 5 | | | | |10 8@ 5 | | | | |8 5@ 3 | | |5 2@ 1 |2 1@ 1 |1 0@ 0 0
用bisect.insort插入新元素
在得到一个有序的序列之后,我们插入新元素仍然想保持有序的序列
那么我们用insort(seq,item)把变量item插入到seq中,并保持seq升序顺序.
例子:
import random import bisect SIZE=7 my_list=[] for i in range(SIZE*2): new_item = random.randrange(SIZE * 3) bisect.insort(my_list,new_item) print('%2d ->'%new_item,my_list)
容器的选择
并不是选择序列容器的时候都要选择列表,虽然列表很强大,但是我们在选择的时候需要根据我们的需求来加以衡量.
比如我们存放一个1000万个浮点数的话,数组(array)的效率要高得多.因为数据背后存的并不是float对象,而是数字的机器翻译也就是字节表述.
当然如果要频繁的对序列做先进先出的操作,那么可以用deque双端队列.